Skip to content

Commit 302eef1

Browse files
committed
fix: unpin on first execution of cursor creating command
1 parent 13211ee commit 302eef1

File tree

10 files changed

+71
-14
lines changed

10 files changed

+71
-14
lines changed

src/cursor/abstract_cursor.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -762,7 +762,7 @@ function cleanupCursor(
762762
}
763763

764764
if (!session.inTransaction()) {
765-
maybeClearPinnedConnection(session, { error });
765+
maybeClearPinnedConnection(session, { error, force: true });
766766
}
767767
}
768768

@@ -779,7 +779,7 @@ function cleanupCursor(
779779
}
780780

781781
if (!session.inTransaction()) {
782-
maybeClearPinnedConnection(session, { error });
782+
maybeClearPinnedConnection(session, { error, force: true });
783783
}
784784
}
785785

src/operations/aggregate.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ export class AggregateOperation<T = Document> extends CommandOperation<T> {
7979
}
8080
}
8181

82+
get isCursorCreating(): boolean {
83+
return true;
84+
}
85+
8286
get canRetryRead(): boolean {
8387
return !this.hasWriteStage;
8488
}

src/operations/estimated_document_count.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ export class EstimatedDocumentCountOperation extends CommandOperation<number> {
2828
this.collectionName = collection.collectionName;
2929
}
3030

31+
get isCursorCreating(): boolean {
32+
return true;
33+
}
34+
3135
execute(server: Server, session: ClientSession, callback: Callback<number>): void {
3236
if (maxWireVersion(server) < 12) {
3337
return this.executeLegacy(server, session, callback);

src/operations/execute_operation.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
isRetryableError,
55
MONGODB_ERROR_CODES,
66
MongoDriverError,
7+
MongoNetworkError,
78
MongoCompatibilityError,
89
MongoServerError
910
} from '../error';
@@ -181,16 +182,31 @@ function executeWithServerSelection(
181182
}
182183

183184
// select a new server, and attempt to retry the operation
184-
topology.selectServer(readPreference, serverSelectionOptions, (err?: any, server?: any) => {
185+
topology.selectServer(readPreference, serverSelectionOptions, (e?: any, server?: any) => {
185186
if (
186-
err ||
187+
e ||
187188
(operation.hasAspect(Aspect.READ_OPERATION) && !supportsRetryableReads(server)) ||
188189
(operation.hasAspect(Aspect.WRITE_OPERATION) && !supportsRetryableWrites(server))
189190
) {
190-
callback(err);
191+
callback(e);
191192
return;
192193
}
193194

195+
// If we have a cursor and the initial command fails with a network error,
196+
// we can retry it on another connection. So we need to check it back in, clear the
197+
// pool for the service id, and retry again.
198+
if (
199+
err &&
200+
err instanceof MongoNetworkError &&
201+
server.loadBalanced &&
202+
session &&
203+
session.isPinned &&
204+
!session.inTransaction() &&
205+
operation.isCursorCreating
206+
) {
207+
session.unpin({ force: true, forceClear: true });
208+
}
209+
194210
operation.execute(server, session, callback);
195211
});
196212
}

src/operations/find.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ export class FindOperation extends CommandOperation<Document> {
101101
this.filter = filter != null && filter._bsontype === 'ObjectID' ? { _id: filter } : filter;
102102
}
103103

104+
get isCursorCreating(): boolean {
105+
return true;
106+
}
107+
104108
execute(server: Server, session: ClientSession, callback: Callback<Document>): void {
105109
this.server = server;
106110

src/operations/find_one.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ export class FindOneOperation extends CommandOperation<Document> {
2121
this.query = query;
2222
}
2323

24+
get isCursorCreating(): boolean {
25+
return true;
26+
}
27+
2428
execute(server: Server, session: ClientSession, callback: Callback<Document>): void {
2529
const coll = this.collection;
2630
const query = this.query;

src/operations/indexes.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,10 @@ export class ListIndexesOperation extends CommandOperation<Document> {
383383
this.collectionNamespace = collection.s.namespace;
384384
}
385385

386+
get isCursorCreating(): boolean {
387+
return true;
388+
}
389+
386390
execute(server: Server, session: ClientSession, callback: Callback<Document>): void {
387391
const serverWireVersion = maxWireVersion(server);
388392
if (serverWireVersion < LIST_INDEXES_WIRE_VERSION) {

src/operations/list_collections.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ export class ListCollectionsOperation extends CommandOperation<string[]> {
4040
}
4141
}
4242

43+
get isCursorCreating(): boolean {
44+
return true;
45+
}
46+
4347
execute(server: Server, session: ClientSession, callback: Callback<string[]>): void {
4448
if (maxWireVersion(server) < LIST_COLLECTIONS_WIRE_VERSION) {
4549
let filter = this.filter;

src/operations/operation.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ export abstract class AbstractOperation<TResult = any> {
8989
return this[kSession];
9090
}
9191

92+
get isCursorCreating(): boolean {
93+
return false;
94+
}
95+
9296
get canRetryRead(): boolean {
9397
return true;
9498
}

src/sessions.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ export interface EndSessionOptions {
9595
*/
9696
error?: AnyError;
9797
force?: boolean;
98+
forceClear?: boolean;
9899
}
99100

100101
/**
@@ -225,7 +226,7 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
225226
}
226227

227228
/** @internal */
228-
unpin(options?: { force?: boolean; error?: AnyError }): void {
229+
unpin(options?: { force?: boolean; forceClear?: boolean; error?: AnyError }): void {
229230
if (this.loadBalanced) {
230231
return maybeClearPinnedConnection(this, options);
231232
}
@@ -479,16 +480,28 @@ export function maybeClearPinnedConnection(
479480

480481
// NOTE: the spec talks about what to do on a network error only, but the tests seem to
481482
// to validate that we don't unpin on _all_ errors?
482-
if (conn && (options?.error == null || options?.force)) {
483+
if (conn) {
483484
const servers = Array.from(session.topology.s.servers.values());
484485
const loadBalancer = servers[0];
485-
loadBalancer.s.pool.checkIn(conn);
486-
conn.emit(
487-
Connection.UNPINNED,
488-
session.transaction.state !== TxnState.NO_TRANSACTION
489-
? ConnectionPoolMetrics.TXN
490-
: ConnectionPoolMetrics.CURSOR
491-
);
486+
487+
if (options?.error == null || options?.force) {
488+
loadBalancer.s.pool.checkIn(conn);
489+
conn.emit(
490+
Connection.UNPINNED,
491+
session.transaction.state !== TxnState.NO_TRANSACTION
492+
? ConnectionPoolMetrics.TXN
493+
: ConnectionPoolMetrics.CURSOR
494+
);
495+
496+
if (options?.forceClear) {
497+
loadBalancer.s.pool.clear(conn.serviceId);
498+
// NOTE: This is not truly necessary, but in the test suite it is. Clearing
499+
// the connection pool will force the connection to get destroyed on the next check
500+
// out, but in the test suite we may not check out a connection again after
501+
// the assertions are finished.
502+
conn.destroy();
503+
}
504+
}
492505

493506
session[kPinnedConnection] = undefined;
494507
}

0 commit comments

Comments
 (0)