Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(CSOT) - feature branch #4095

Open
wants to merge 68 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
ab21b5b
Install timeout throughout operation layer
W-A-James Apr 11, 2024
2338dff
update with timeout
W-A-James Apr 24, 2024
9615138
start prose test impl
W-A-James Apr 24, 2024
0eb1986
add timeout to find.execute
W-A-James Apr 26, 2024
69f1087
start implementing prose tests
W-A-James Apr 26, 2024
ff2ec44
don't construct Timeout when not needed
W-A-James Apr 26, 2024
a3e552a
ensure that timeoutMS is passed down correctly
W-A-James Apr 26, 2024
a9c96ac
start working on unit tests
W-A-James Apr 26, 2024
21b2fcd
continue prose test implementation
W-A-James Apr 26, 2024
892800d
revert spec test changes
W-A-James Apr 26, 2024
6f62c9f
revert spec test changes
W-A-James Apr 26, 2024
b878c6c
revert spec test changes
W-A-James Apr 26, 2024
c5fae5c
support timeout on run_command
W-A-James May 1, 2024
124f2d7
continue prose test implementation
W-A-James May 1, 2024
cd6a27e
prose test changes
W-A-James May 1, 2024
b63b63f
WIP - server selection changes
W-A-James May 1, 2024
9f4e884
revert unneeded connection changes
W-A-James May 3, 2024
6fc1198
add serverSelectionTimeout to run_command
W-A-James May 3, 2024
c18b7f0
Merge branch 'main' into NODE-6090
W-A-James May 3, 2024
9c40f94
use correct timeout
W-A-James May 3, 2024
a958ad8
Merge branch 'NODE-6090' of github.com:mongodb/node-mongodb-native in…
W-A-James May 3, 2024
a75d9df
reorder operations
W-A-James May 3, 2024
875ea67
formatting
W-A-James May 3, 2024
3839330
skip some CSOT tests that cannot be made to pass here
W-A-James May 3, 2024
c738691
Improve timeout messages
W-A-James May 3, 2024
c4ae2fb
silence eslint test issues
W-A-James May 3, 2024
bf5a37a
bump timeout values
W-A-James May 3, 2024
e2f9125
misc changes
W-A-James May 3, 2024
472fa9e
rename timeout
W-A-James May 3, 2024
43e69bd
make getter internal
W-A-James May 3, 2024
fb96314
rename timeout
W-A-James May 3, 2024
ad55e9c
remove unneeded change for this PR
W-A-James May 3, 2024
6c7adf1
clear server selection timeout after checkout and remove command exec…
W-A-James May 6, 2024
f586736
move Timeout.min to independent helper function
W-A-James May 6, 2024
021a94d
move Timeout.min to independent helper function
W-A-James May 6, 2024
a61a9d0
Merge branch 'main' into NODE-6090
W-A-James May 7, 2024
30e564c
update timeout propagation
W-A-James May 7, 2024
02509a1
clean up
W-A-James May 7, 2024
7c73c58
cleanup
W-A-James May 7, 2024
b92162b
test cleanup
W-A-James May 7, 2024
4625af4
clean up
W-A-James May 7, 2024
7d7f005
simplify calculation
W-A-James May 7, 2024
ecdd66d
cleanup
W-A-James May 7, 2024
9307a80
clarify branching timeout behaviour
W-A-James May 7, 2024
78ccbd8
Merge branch 'main' into NODE-6090
aditi-khare-mongoDB May 8, 2024
c4d26c5
operationTimeout -> timeout
W-A-James May 8, 2024
3590e5e
Merge branch 'NODE-6090' of github.com:mongodb/node-mongodb-native in…
W-A-James May 8, 2024
f7cc3a5
ensure timeouts are properly cleared
W-A-James May 8, 2024
a1c7601
don't race if given infinite timeout
W-A-James May 8, 2024
2ed34ac
default clearTimeout to false
W-A-James May 9, 2024
7111909
remove clearTimeout variable
W-A-James May 9, 2024
86a7eeb
conditionally clear timeout on early return
W-A-James May 9, 2024
2a816e3
fix unit tests
W-A-James May 9, 2024
62bb3ca
bump test timeout value
W-A-James May 9, 2024
cdce963
replace test with sinon fake timer test
W-A-James May 10, 2024
5c91311
Update src/sdam/topology.ts
W-A-James May 10, 2024
72c22b9
clean up logic
W-A-James May 10, 2024
68f1eec
Update test to assert on current behaviour
W-A-James May 10, 2024
e88191a
Merge branch 'main' into NODE-6090
W-A-James May 23, 2024
daf0d5a
fix autoconnect
W-A-James May 23, 2024
1d9ac3e
add test
W-A-James May 23, 2024
e468b54
remove .only
W-A-James May 23, 2024
fd6c751
fix test
W-A-James May 24, 2024
bab1667
Merge branch 'main' into NODE-6090
W-A-James May 24, 2024
4d126eb
ensure test only runs when failcommand is available
W-A-James May 24, 2024
92564d8
do not run on 4.2
W-A-James May 28, 2024
09a8b17
fix csot test?
W-A-James May 28, 2024
f2f4d63
Merge branch 'main' into NODE-6090
W-A-James May 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ export class Admin {
new RunAdminCommandOperation(command, {
...resolveBSONOptions(options),
session: options?.session,
readPreference: options?.readPreference
readPreference: options?.readPreference,
timeoutMS: options?.timeoutMS ?? this.s.db.timeoutMS
Comment on lines +81 to +82
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOTE FOR REVIEWERS: Needed to do this to ensure that unit tests using ping work

})
);
}
Expand Down
75 changes: 67 additions & 8 deletions src/cmap/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ import {
MongoInvalidArgumentError,
MongoNetworkError,
MongoNetworkTimeoutError,
MongoOperationTimeoutError,
MongoRuntimeError,
needsRetryableWriteLabel
} from '../error';
import { Timeout, TimeoutError } from '../timeout';
import { HostAddress, ns, promiseWithResolvers } from '../utils';
import { AuthContext } from './auth/auth_provider';
import { AuthMechanism } from './auth/providers';
Expand All @@ -37,6 +39,7 @@ export type Stream = Socket | TLSSocket;

export async function connect(options: ConnectionOptions): Promise<Connection> {
let connection: Connection | null = null;
console.log(options.timeout);
try {
const socket = await makeSocket(options);
connection = makeConnection(options, socket);
Expand Down Expand Up @@ -110,7 +113,25 @@ export async function performInitialHandshake(
}

const start = new Date().getTime();
const response = await conn.command(ns('admin.$cmd'), handshakeDoc, handshakeOptions);

let response: Document;
if (options.timeout) {
console.log(options.timeout);
try {
response = await Promise.race([
options.timeout,
conn.command(ns('admin.$cmd'), handshakeDoc, handshakeOptions)
]);
} catch (error) {
if (TimeoutError.is(error)) {
throw new MongoOperationTimeoutError('Timed out during handshake');
} else {
throw error;
}
}
W-A-James marked this conversation as resolved.
Show resolved Hide resolved
} else {
response = await conn.command(ns('admin.$cmd'), handshakeDoc, handshakeOptions);
}

if (!('isWritablePrimary' in response)) {
// Provide hello-style response document.
Expand Down Expand Up @@ -321,14 +342,40 @@ export async function makeSocket(options: MakeConnectionOptions): Promise<Stream
const connectTimeoutMS = options.connectTimeoutMS ?? 30000;
const existingSocket = options.existingSocket;

let timeout: Timeout | null;
if (options.timeout) {
options.timeout.throwIfExpired();
timeout = Timeout.expires(Timeout.min(connectTimeoutMS, options.timeout.remainingTime));
} else {
timeout = null;
}
W-A-James marked this conversation as resolved.
Show resolved Hide resolved

let socket: Stream;

if (options.proxyHost != null) {
// Currently, only Socks5 is supported.
return await makeSocks5Connection({
...options,
connectTimeoutMS // Should always be present for Socks5
});
if (timeout) {
try {
timeout.throwIfExpired();
return await Promise.race([
timeout,
makeSocks5Connection({
...options,
connectTimeoutMS // Should always be present for Socks5
})
]);
} catch (error) {
if (TimeoutError.is(error)) {
throw new MongoOperationTimeoutError('Failed during socket creation');
} else {
throw error;
}
}
} else
return await makeSocks5Connection({
...options,
connectTimeoutMS // Should always be present for Socks5
});
}

if (useTLS) {
Expand Down Expand Up @@ -370,11 +417,22 @@ export async function makeSocket(options: MakeConnectionOptions): Promise<Stream
}

try {
socket = await connectedSocket;
return socket;
// TODO: race Promises here
if (timeout) {
timeout.throwIfExpired();
socket = await Promise.race([timeout, connectedSocket]);
return socket;
} else {
socket = await connectedSocket;
return socket;
}
} catch (error) {
socket.destroy();
throw error;
if (TimeoutError.is(error)) {
throw new MongoOperationTimeoutError('Timed out during socket creation');
} else {
throw error;
}
} finally {
socket.setTimeout(0);
socket.removeAllListeners();
Expand All @@ -397,6 +455,7 @@ function loadSocks() {
}

async function makeSocks5Connection(options: MakeConnectionOptions): Promise<Stream> {
// TODO: ADD CSOT support here
const hostAddress = HostAddress.fromHostPort(
options.proxyHost ?? '', // proxyHost is guaranteed to set here
options.proxyPort ?? 1080
Expand Down
8 changes: 8 additions & 0 deletions src/cmap/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { type CancellationToken, TypedEventEmitter } from '../mongo_types';
import { ReadPreference, type ReadPreferenceLike } from '../read_preference';
import { ServerType } from '../sdam/common';
import { applySession, type ClientSession, updateSessionFromResponse } from '../sessions';
import { type Timeout } from '../timeout';
import {
BufferPool,
calculateDurationInMs,
Expand Down Expand Up @@ -88,6 +89,11 @@ export interface CommandOptions extends BSONSerializeOptions {
writeConcern?: WriteConcern;

directConnection?: boolean;

/** @internal */
timeout?: Timeout;
/** @internal */
serverSelectionTimeout?: Timeout;
W-A-James marked this conversation as resolved.
Show resolved Hide resolved
}
nbbeeken marked this conversation as resolved.
Show resolved Hide resolved

/** @public */
Expand Down Expand Up @@ -126,6 +132,8 @@ export interface ConnectionOptions
extendedMetadata: Promise<Document>;
/** @internal */
mongoLogger?: MongoLogger | undefined;
/** @internal */
timeout?: Timeout;
}

/** @public */
Expand Down
25 changes: 18 additions & 7 deletions src/cmap/connection_pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
MongoInvalidArgumentError,
MongoMissingCredentialsError,
MongoNetworkError,
MongoOperationTimeoutError,
MongoRuntimeError,
MongoServerError
} from '../error';
Expand Down Expand Up @@ -354,7 +355,7 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
* will be held by the pool. This means that if a connection is checked out it MUST be checked back in or
* explicitly destroyed by the new owner.
*/
async checkOut(): Promise<Connection> {
async checkOut(options?: { timeout?: Timeout }): Promise<Connection> {
this.emitAndLog(
ConnectionPool.CONNECTION_CHECK_OUT_STARTED,
new ConnectionCheckOutStartedEvent(this)
Expand All @@ -364,7 +365,13 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {

const { promise, resolve, reject } = promiseWithResolvers<Connection>();

const timeout = Timeout.expires(waitQueueTimeoutMS);
let timeout: Timeout;
if (options?.timeout) {
options.timeout.throwIfExpired();
timeout = options.timeout;
} else {
timeout = Timeout.expires(waitQueueTimeoutMS);
}

const waitQueueMember: WaitQueueMember = {
resolve,
Expand All @@ -373,7 +380,7 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
};

this[kWaitQueue].push(waitQueueMember);
process.nextTick(() => this.processWaitQueue());
process.nextTick(() => this.processWaitQueue({ timeout }));

try {
return await Promise.race([promise, waitQueueMember.timeout]);
Expand All @@ -393,6 +400,9 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
: 'Timed out while checking out a connection from connection pool',
this.address
);
if (options?.timeout) {
throw new MongoOperationTimeoutError('Timed out', { cause: timeoutError });
W-A-James marked this conversation as resolved.
Show resolved Hide resolved
}
throw timeoutError;
}
throw error;
Expand Down Expand Up @@ -616,14 +626,15 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
return true;
}

private createConnection(callback: Callback<Connection>) {
private createConnection(callback: Callback<Connection>, options?: { timeout?: Timeout }) {
const connectOptions: ConnectionOptions = {
...this.options,
id: this[kConnectionCounter].next().value,
generation: this[kGeneration],
cancellationToken: this[kCancellationToken],
mongoLogger: this.mongoLogger,
authProviders: this[kServer].topology.client.s.authProviders
authProviders: this[kServer].topology.client.s.authProviders,
timeout: options?.timeout
};

this[kPending]++;
Expand Down Expand Up @@ -735,7 +746,7 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
}
}

private processWaitQueue() {
private processWaitQueue(options?: { timeout?: Timeout }) {
W-A-James marked this conversation as resolved.
Show resolved Hide resolved
if (this[kProcessingWaitQueue]) {
return;
}
Expand Down Expand Up @@ -823,7 +834,7 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
waitQueueMember.timeout.clear();
}
process.nextTick(() => this.processWaitQueue());
});
}, options);
}
this[kProcessingWaitQueue] = false;
}
Expand Down
4 changes: 4 additions & 0 deletions src/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,10 @@ export class Collection<TSchema extends Document = Document> {
this.s.collectionHint = normalizeHintField(v);
}

get timeoutMS(): number | undefined {
return this.s.options.timeoutMS;
}

/**
* Inserts a single document into MongoDB. If documents passed in do not contain the **_id** field,
* one will be added to each of the documents missing it by the driver, mutating the document. This behavior
Expand Down
5 changes: 5 additions & 0 deletions src/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,10 @@ export class Db {
return this.s.namespace.toString();
}

get timeoutMS(): number | undefined {
return this.s.options?.timeoutMS;
}

/**
* Create a new collection on a server with the specified options. Use this to create capped collections.
* More information about command options available at https://www.mongodb.com/docs/manual/reference/command/create/
Expand Down Expand Up @@ -272,6 +276,7 @@ export class Db {
this.client,
new RunCommandOperation(this, command, {
...resolveBSONOptions(options),
timeoutMS: options?.timeoutMS,
session: options?.session,
readPreference: options?.readPreference
})
Expand Down
6 changes: 6 additions & 0 deletions src/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,12 @@ export class MongoUnexpectedServerResponseError extends MongoRuntimeError {
}
}

export class MongoOperationTimeoutError extends MongoRuntimeError {
nbbeeken marked this conversation as resolved.
Show resolved Hide resolved
override get name(): string {
return 'MongoOperationTimeoutError';
}
}

/**
* An error thrown when the user attempts to add options to a cursor that has already been
* initialized
Expand Down
3 changes: 3 additions & 0 deletions src/operations/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export interface OperationParent {
writeConcern?: WriteConcern;
readPreference?: ReadPreference;
bsonOptions?: BSONSerializeOptions;
timeoutMS?: number;
}

/** @internal */
Expand Down Expand Up @@ -117,6 +118,8 @@ export abstract class CommandOperation<T> extends AbstractOperation<T> {
const options = {
...this.options,
...this.bsonOptions,
timeout: this.timeout,
serverSelectionTimeout: this.serverSelectionTimeout,
readPreference: this.readPreference,
session
};
Expand Down
16 changes: 15 additions & 1 deletion src/operations/execute_operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
} from '../sdam/server_selection';
import type { Topology } from '../sdam/topology';
import type { ClientSession } from '../sessions';
import { Timeout } from '../timeout';
import { squashError, supportsRetryableWrites } from '../utils';
import { AbstractOperation, Aspect } from './operation';

Expand Down Expand Up @@ -151,9 +152,21 @@ export async function executeOperation<
selector = readPreference;
}

const timeout = operation.timeout;
timeout?.throwIfExpired();
// TODO: construct serverSelection timeout here, pass it into topology.selectServer and store on
// operation for use in operation.execute when we have to perform connection checkout
operation.serverSelectionTimeout =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thoughts on consolidating this logic into a function or something? We have similar logic in the topology

operation.timeout != null
? Timeout.expires(
Timeout.min(operation.timeout.remainingTime, topology.s.serverSelectionTimeoutMS)
)
: undefined;

const server = await topology.selectServer(selector, {
session,
operationName: operation.commandName
operationName: operation.commandName,
timeout: operation.serverSelectionTimeout
});

if (session == null) {
Expand Down Expand Up @@ -264,6 +277,7 @@ async function retryOperation<
// select a new server, and attempt to retry the operation
const server = await topology.selectServer(selector, {
session,
timeout: operation.timeout,
operationName: operation.commandName,
previousServer
});
Expand Down
1 change: 1 addition & 0 deletions src/operations/find.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ export class FindOperation extends CommandOperation<Document> {
return await server.command(this.ns, findCommand, {
...this.options,
...this.bsonOptions,
timeout: this.timeout,
documentsReturnedIn: 'firstBatch',
session
});
Expand Down
11 changes: 11 additions & 0 deletions src/operations/operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { type BSONSerializeOptions, type Document, resolveBSONOptions } from '..
import { ReadPreference, type ReadPreferenceLike } from '../read_preference';
import type { Server } from '../sdam/server';
import type { ClientSession } from '../sessions';
import { Timeout } from '../timeout';
import type { MongoDBNamespace } from '../utils';

export const Aspect = {
Expand Down Expand Up @@ -61,6 +62,12 @@ export abstract class AbstractOperation<TResult = any> {

options: OperationOptions;

/** @internal */
timeout?: Timeout;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused about the purpose of the timeout. I commented in Server.ts, but at no point that I know of will we ever use a generic "CSOT" timeout. Instead, at each point where we apply CSOT's logic to async operations, we will create custom timeouts that apply CSOT logic (i.e., a timeout that applies to socket read specifically or socket write). Am I misunderstanding?

Given that, it feels odd to create a promise that we won't use. And it seems odd to use our generic Timeout class to store CSOT-specific context (Timeout.min, Timeout.remainingTime).

Thoughts on a generic CSOTContext class? That's basically what the TimeoutControllerFactory was in the initial design. Something that holds CSOT context, is instantiated at operation construction time, and generates timeouts for certain parts of the driver (i.e., context.timeoutForServerSelection(), which encapsulates CSOT logic for server selection into a single, unit testable class).

(this relates to my other comment about consolidating timeout logic for server selection - we'll need it eventually for change streams too so it makes sense not to implement it three times).

Copy link
Contributor Author

@W-A-James W-A-James May 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea behind adding the timeout field to AbstractOperation was to start the timeout at operation construction and to use it to construct the other timeouts we'd use to implement CSOT.

I'd agree that the Timeout.min static function should probably be its own function, but remainingTime seems to me to be applicable to a generic timeout. That it happens to satisfy the requirements for CSOT doesn't necessarily make it CSOT-specific.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CSOTContext class sounds somewhat similar to the OperationContext idea that you had explored earlier on in the CSOT design. I'm not against it on principle, but I think we scrapped that idea at some point. Will look for any documents we had on why and report back.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to close the loop here: We do use the "csot" timeout when it is the lower bound of time remaining before the operation is meant to expire. Server selection and connection checkout need to never take more than serverSelectionTimeoutMS but can be given less time if a csot timeout is smaller than that setting. So the logic in this PR does some gte/lte checks and determines if we need to make a new timer to maintain that requirement.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CSOTContext class sounds somewhat similar to the OperationContext idea that you had explored earlier on in the CSOT design. I'm not against it on principle, but I think we scrapped that idea at some point. Will look for any documents we had on why and report back.

The context would be similar to the timeout factory, not the operation context. We decided not to implement an operation context because we can pass CSOT-related data on the options objects in the driver. I'm proposing a CSOT context to encapsulate CSOT logic, which we would then pass through the driver on the options objects.


I do not consider this thread resolved. I think the current implementation is more complicated than necessary because we only sometimes re-use the timeout.

  • This means server selection and connection checkout must be responsible for determining when to reuse the timeout.
  • We can't unconditionally clear the timeout because it may be re-used later.

Instead, I propose we either:

  1. Always create a new timeout for server selection and connection checkout.
  2. Always reuse the same Timeout object, but just reset the Timeout's interval for server selection and connection checkout.

Regardless of which is chosen, I think the resultant code is simpler because server selection and connection checkout 1) do not worry about whether or not they need to use a cached timeout or create a new one 2) they can always clear the timeout.

This works especially nicely with the TimeoutFactory or a TimeoutContext, because we can encapsulate all timeout related logic into a single place that's easily unit testable. I'm partial to the factory approach:

class TimeoutFactory {
  private timeoutMS: number | null;
  private started = now();

  getTimeoutForServerSelection(): Timeout {
    // returns a timeout, handling CSOT vs Legacy timeout logic
  }
}

class Topology {

  selectServer(options: { ..., timeoutFactory: TimeoutFactory }) {
    ...
    const timeout = timeoutFactory.getTimeoutForServerSelection();
    try {
		....
	} finally {
      timeout.clear();
    }
  }
}

Note that with an approach like this, whether or not we reuse a timeout can easily be encapsulated into the TimeoutFactory by instantiating a timeout when the factory is constructed and returning the cached timeout where needed.

But a context class could suffice too:

class TimeoutContext {
  private timeoutMS: number | null;
  private started = now();

  getTimeoutForServerSelection(): number {}
}


class Topology {

  selectServer(options: { ..., timeoutContext: TimeoutContext }) {
    ...
    const timeout = Timeout.expires(timeoutFactory.getTimeoutForServerSelection());
    try {
		....
	} finally {
      timeout.clear();
    }
  }
}

An approach like this consolidates CSOT logic and can be reused outside of the main code path (i.e., topology connect).

I don't think this work needs to block this PR. But I do want to make sure we discuss this, and I'd like to consider one of these approaches in a future ticket.


/** @internal */
serverSelectionTimeout?: Timeout;
W-A-James marked this conversation as resolved.
Show resolved Hide resolved

nbbeeken marked this conversation as resolved.
Show resolved Hide resolved
[kSession]: ClientSession | undefined;

constructor(options: OperationOptions = {}) {
Expand All @@ -76,6 +83,10 @@ export abstract class AbstractOperation<TResult = any> {
this.options = options;
this.bypassPinningCheck = !!options.bypassPinningCheck;
this.trySecondaryWrite = false;

if (options.timeoutMS != null) {
this.timeout = Timeout.expires(options.timeoutMS);
}
}

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