Skip to content

Commit ee851a2

Browse files
committed
add tests
1 parent b4fa0ce commit ee851a2

File tree

12 files changed

+344
-119
lines changed

12 files changed

+344
-119
lines changed

src/cmap/connection.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { type CancellationToken, TypedEventEmitter } from '../mongo_types';
2929
import { ReadPreference, type ReadPreferenceLike } from '../read_preference';
3030
import { ServerType } from '../sdam/common';
3131
import { applySession, type ClientSession, updateSessionFromResponse } from '../sessions';
32-
import { type Timeout } from '../timeout';
32+
import { type TimeoutContext } from '../timeout';
3333
import {
3434
BufferPool,
3535
calculateDurationInMs,
@@ -95,7 +95,7 @@ export interface CommandOptions extends BSONSerializeOptions {
9595
directConnection?: boolean;
9696

9797
/** @internal */
98-
timeout?: Timeout;
98+
timeoutContext?: TimeoutContext; //TODO(NODE-6187): Make this required
9999
}
100100

101101
/** @public */

src/cmap/connection_pool.ts

Lines changed: 9 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ import {
2727
} from '../error';
2828
import { CancellationToken, TypedEventEmitter } from '../mongo_types';
2929
import type { Server } from '../sdam/server';
30-
import { Timeout, TimeoutError } from '../timeout';
31-
import { type Callback, csotMin, List, makeCounter, promiseWithResolvers } from '../utils';
30+
import { Timeout, type TimeoutContext, TimeoutError } from '../timeout';
31+
import { type Callback, List, makeCounter, promiseWithResolvers } from '../utils';
3232
import { connect } from './connect';
3333
import { Connection, type ConnectionEvents, type ConnectionOptions } from './connection';
3434
import {
@@ -354,41 +354,17 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
354354
* will be held by the pool. This means that if a connection is checked out it MUST be checked back in or
355355
* explicitly destroyed by the new owner.
356356
*/
357-
async checkOut(options?: { timeout?: Timeout }): Promise<Connection> {
357+
async checkOut(options?: { timeoutContext?: TimeoutContext }): Promise<Connection> {
358358
this.emitAndLog(
359359
ConnectionPool.CONNECTION_CHECK_OUT_STARTED,
360360
new ConnectionCheckOutStartedEvent(this)
361361
);
362362

363-
const waitQueueTimeoutMS = this.options.waitQueueTimeoutMS;
364-
const serverSelectionTimeoutMS = this[kServer].topology.s.serverSelectionTimeoutMS;
365-
366363
const { promise, resolve, reject } = promiseWithResolvers<Connection>();
367364

368-
let timeout: Timeout | null = null;
369-
if (options?.timeout) {
370-
// CSOT enabled
371-
// Determine if we're using the timeout passed in or a new timeout
372-
if (options.timeout.duration > 0 || serverSelectionTimeoutMS > 0) {
373-
// This check determines whether or not Topology.selectServer used the configured
374-
// `timeoutMS` or `serverSelectionTimeoutMS` value for its timeout
375-
if (
376-
options.timeout.duration === serverSelectionTimeoutMS ||
377-
csotMin(options.timeout.duration, serverSelectionTimeoutMS) < serverSelectionTimeoutMS
378-
) {
379-
// server selection used `timeoutMS`, so we should use the existing timeout as the timeout
380-
// here
381-
timeout = options.timeout;
382-
} else {
383-
// server selection used `serverSelectionTimeoutMS`, so we construct a new timeout with
384-
// the time remaining to ensure that Topology.selectServer and ConnectionPool.checkOut
385-
// cumulatively don't spend more than `serverSelectionTimeoutMS` blocking
386-
timeout = Timeout.expires(serverSelectionTimeoutMS - options.timeout.timeElapsed);
387-
}
388-
}
389-
} else {
390-
timeout = Timeout.expires(waitQueueTimeoutMS);
391-
}
365+
let timeout;
366+
if (options?.timeoutContext) timeout = options.timeoutContext.connectionCheckoutTimeout;
367+
else timeout = Timeout.expires(this.options.waitQueueTimeoutMS);
392368

393369
const waitQueueMember: WaitQueueMember = {
394370
resolve,
@@ -403,6 +379,7 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
403379
return await (timeout ? Promise.race([promise, timeout]) : promise);
404380
} catch (error) {
405381
if (TimeoutError.is(error)) {
382+
timeout?.clear();
406383
waitQueueMember[kCancelled] = true;
407384

408385
this.emitAndLog(
@@ -415,7 +392,7 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
415392
: 'Timed out while checking out a connection from connection pool',
416393
this.address
417394
);
418-
if (options?.timeout) {
395+
if (options?.timeoutContext?.csotEnabled()) {
419396
throw new MongoOperationTimeoutError('Timed out during connection checkout', {
420397
cause: timeoutError
421398
});
@@ -424,7 +401,7 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
424401
}
425402
throw error;
426403
} finally {
427-
if (timeout !== options?.timeout) timeout?.clear();
404+
if (options?.timeoutContext?.clearConnectionCheckoutTimeout) timeout?.clear();
428405
}
429406
}
430407

src/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,13 @@ export type {
556556
WithTransactionCallback
557557
} from './sessions';
558558
export type { Sort, SortDirection, SortDirectionForCmd, SortForCmd } from './sort';
559-
export type { Timeout } from './timeout';
559+
export type {
560+
CSOTTimeoutContext,
561+
LegacyTimeoutContext,
562+
Timeout,
563+
TimeoutContext,
564+
TimeoutContextOptions
565+
} from './timeout';
560566
export type { Transaction, TransactionOptions, TxnState } from './transactions';
561567
export type {
562568
BufferPool,

src/operations/command.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export abstract class CommandOperation<T> extends AbstractOperation<T> {
118118
const options = {
119119
...this.options,
120120
...this.bsonOptions,
121-
timeout: this.timeout,
121+
timeoutContext: this.timeoutContext,
122122
readPreference: this.readPreference,
123123
session
124124
};

src/operations/execute_operation.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
} from '../sdam/server_selection';
2828
import type { Topology } from '../sdam/topology';
2929
import type { ClientSession } from '../sessions';
30-
import { Timeout } from '../timeout';
30+
import { TimeoutContext } from '../timeout';
3131
import { squashError, supportsRetryableWrites } from '../utils';
3232
import { AbstractOperation, Aspect } from './operation';
3333

@@ -118,6 +118,14 @@ export async function executeOperation<
118118
);
119119
}
120120

121+
const timeoutContext = TimeoutContext.create({
122+
serverSelectionTimeoutMS: client.options.serverSelectionTimeoutMS,
123+
waitQueueTimeoutMS: client.options.waitQueueTimeoutMS,
124+
timeoutMS: operation.options.timeoutMS
125+
});
126+
127+
operation.timeoutContext = timeoutContext;
128+
121129
const readPreference = operation.readPreference ?? ReadPreference.primary;
122130
const inTransaction = !!session?.inTransaction();
123131

@@ -153,13 +161,10 @@ export async function executeOperation<
153161
selector = readPreference;
154162
}
155163

156-
const timeout = operation.timeoutMS != null ? Timeout.expires(operation.timeoutMS) : undefined;
157-
operation.timeout = timeout;
158-
159164
const server = await topology.selectServer(selector, {
160165
session,
161166
operationName: operation.commandName,
162-
timeout
167+
timeoutContext: operation.timeoutContext
163168
});
164169

165170
if (session == null) {
@@ -272,7 +277,8 @@ async function retryOperation<
272277
session,
273278
timeout: operation.timeout,
274279
operationName: operation.commandName,
275-
previousServer
280+
previousServer,
281+
timeoutContext: operation.timeoutContext
276282
});
277283

278284
if (isWriteOperation && !supportsRetryableWrites(server)) {

src/operations/find.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export class FindOperation extends CommandOperation<Document> {
113113
...this.bsonOptions,
114114
documentsReturnedIn: 'firstBatch',
115115
session,
116-
timeout: this.timeout
116+
timeoutContext: this.timeoutContext
117117
},
118118
undefined
119119
);

src/operations/operation.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { type BSONSerializeOptions, type Document, resolveBSONOptions } from '..
22
import { ReadPreference, type ReadPreferenceLike } from '../read_preference';
33
import type { Server } from '../sdam/server';
44
import type { ClientSession } from '../sessions';
5-
import { type Timeout } from '../timeout';
5+
import { type Timeout, type TimeoutContext } from '../timeout';
66
import type { MongoDBNamespace } from '../utils';
77

88
export const Aspect = {
@@ -67,6 +67,9 @@ export abstract class AbstractOperation<TResult = any> {
6767
/** @internal */
6868
timeoutMS?: number;
6969

70+
/** @internal */
71+
timeoutContext!: TimeoutContext;
72+
7073
[kSession]: ClientSession | undefined;
7174

7275
constructor(options: OperationOptions = {}) {
@@ -82,8 +85,6 @@ export abstract class AbstractOperation<TResult = any> {
8285
this.options = options;
8386
this.bypassPinningCheck = !!options.bypassPinningCheck;
8487
this.trySecondaryWrite = false;
85-
86-
this.timeoutMS = options.timeoutMS;
8788
}
8889

8990
/** Must match the first key of the command object sent to the server.

src/operations/run_command.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export class RunCommandOperation<T = Document> extends AbstractOperation<T> {
3333
const res: TODO_NODE_3286 = await server.command(this.ns, this.command, {
3434
...this.options,
3535
readPreference: this.readPreference,
36-
timeout: this.timeout,
36+
timeoutContext: this.timeoutContext,
3737
session
3838
});
3939
return res;
@@ -62,7 +62,7 @@ export class RunAdminCommandOperation<T = Document> extends AbstractOperation<T>
6262
...this.options,
6363
readPreference: this.readPreference,
6464
session,
65-
timeout: this.timeout
65+
timeoutContext: this.timeoutContext
6666
});
6767
return res;
6868
}

src/sdam/topology.ts

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,10 @@ import { MongoLoggableComponent, type MongoLogger, SeverityLevel } from '../mong
3434
import { TypedEventEmitter } from '../mongo_types';
3535
import { ReadPreference, type ReadPreferenceLike } from '../read_preference';
3636
import type { ClientSession } from '../sessions';
37-
import { Timeout, TimeoutError } from '../timeout';
37+
import { Timeout, TimeoutContext, TimeoutError } from '../timeout';
3838
import type { Transaction } from '../transactions';
3939
import {
4040
type Callback,
41-
csotMin,
4241
type EventEmitterWithState,
4342
HostAddress,
4443
List,
@@ -181,6 +180,8 @@ export interface SelectServerOptions {
181180
previousServer?: ServerDescription;
182181
/** @internal*/
183182
timeout?: Timeout;
183+
/** @internal */
184+
timeoutContext?: TimeoutContext;
184185
}
185186

186187
/** @public */
@@ -459,12 +460,21 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
459460
}
460461

461462
const timeoutMS = this.client.options.timeoutMS;
462-
const timeout = timeoutMS != null ? Timeout.expires(timeoutMS) : undefined;
463+
const serverSelectionTimeoutMS = this.client.options.serverSelectionTimeoutMS;
463464
const readPreference = options.readPreference ?? ReadPreference.primary;
465+
466+
// TODO: figure out if there is a way we can remove this and use the command that initiated the
467+
// autoconnect's timeoutContext
468+
const timeoutContext = TimeoutContext.create({
469+
timeoutMS,
470+
serverSelectionTimeoutMS,
471+
waitQueueTimeoutMS: this.client.options.waitQueueTimeoutMS
472+
});
473+
464474
const selectServerOptions = {
465475
operationName: 'ping',
466-
timeout,
467-
...options
476+
...options,
477+
timeoutContext
468478
};
469479
try {
470480
const server = await this.selectServer(
@@ -474,7 +484,7 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
474484

475485
const skipPingOnConnect = this.s.options[Symbol.for('@@mdb.skipPingOnConnect')] === true;
476486
if (!skipPingOnConnect && server && this.s.credentials) {
477-
await server.command(ns('admin.$cmd'), { ping: 1 }, { timeout });
487+
await server.command(ns('admin.$cmd'), { ping: 1 }, { timeoutContext });
478488
stateTransition(this, STATE_CONNECTED);
479489
this.emit(Topology.OPEN, this);
480490
this.emit(Topology.CONNECT, this);
@@ -563,24 +573,10 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
563573
new ServerSelectionStartedEvent(selector, this.description, options.operationName)
564574
);
565575
}
566-
const serverSelectionTimeoutMS = options.serverSelectionTimeoutMS ?? 0;
567-
let timeout: Timeout | null;
568-
if (options.timeout) {
569-
// CSOT Enabled
570-
if (options.timeout.duration > 0 || serverSelectionTimeoutMS > 0) {
571-
if (
572-
options.timeout.duration === serverSelectionTimeoutMS ||
573-
csotMin(options.timeout.duration, serverSelectionTimeoutMS) < serverSelectionTimeoutMS
574-
) {
575-
timeout = options.timeout;
576-
} else {
577-
timeout = Timeout.expires(serverSelectionTimeoutMS);
578-
}
579-
} else {
580-
timeout = null;
581-
}
582-
} else {
583-
timeout = Timeout.expires(serverSelectionTimeoutMS);
576+
let timeout;
577+
if (options.timeoutContext) timeout = options.timeoutContext.serverSelectionTimeout;
578+
else {
579+
timeout = Timeout.expires(options.serverSelectionTimeoutMS ?? 0);
584580
}
585581

586582
const isSharded = this.description.type === TopologyType.Sharded;
@@ -654,7 +650,7 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
654650
);
655651
}
656652

657-
if (options.timeout) {
653+
if (options.timeoutContext?.csotEnabled()) {
658654
throw new MongoOperationTimeoutError('Timed out during server selection', {
659655
cause: timeoutError
660656
});
@@ -664,7 +660,7 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
664660
// Other server selection error
665661
throw error;
666662
} finally {
667-
if (timeout !== options.timeout) timeout?.clear();
663+
if (options.timeoutContext?.clearServerSelectionTimeout) timeout?.clear();
668664
}
669665
}
670666
/**

0 commit comments

Comments
 (0)