mongodb 6.12.0-dev.20250125.sha.c1bcf0de → 6.12.0-dev.20250129.sha.907aac19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/beta.d.ts +55 -13
- package/lib/client-side-encryption/auto_encrypter.js +4 -2
- package/lib/client-side-encryption/auto_encrypter.js.map +1 -1
- package/lib/client-side-encryption/client_encryption.js +4 -4
- package/lib/client-side-encryption/client_encryption.js.map +1 -1
- package/lib/client-side-encryption/state_machine.js +56 -30
- package/lib/client-side-encryption/state_machine.js.map +1 -1
- package/lib/cmap/connection.js +28 -22
- package/lib/cmap/connection.js.map +1 -1
- package/lib/cmap/connection_pool.js +5 -0
- package/lib/cmap/connection_pool.js.map +1 -1
- package/lib/cmap/wire_protocol/on_data.js +6 -1
- package/lib/cmap/wire_protocol/on_data.js.map +1 -1
- package/lib/collection.js.map +1 -1
- package/lib/cursor/abstract_cursor.js +47 -18
- package/lib/cursor/abstract_cursor.js.map +1 -1
- package/lib/cursor/aggregation_cursor.js +2 -1
- package/lib/cursor/aggregation_cursor.js.map +1 -1
- package/lib/cursor/find_cursor.js +2 -1
- package/lib/cursor/find_cursor.js.map +1 -1
- package/lib/cursor/list_collections_cursor.js +2 -1
- package/lib/cursor/list_collections_cursor.js.map +1 -1
- package/lib/db.js +2 -1
- package/lib/db.js.map +1 -1
- package/lib/mongo_client.js +4 -0
- package/lib/mongo_client.js.map +1 -1
- package/lib/operations/execute_operation.js +7 -3
- package/lib/operations/execute_operation.js.map +1 -1
- package/lib/operations/list_collections.js.map +1 -1
- package/lib/operations/operation.js.map +1 -1
- package/lib/sdam/server.js +26 -16
- package/lib/sdam/server.js.map +1 -1
- package/lib/sdam/topology.js +5 -0
- package/lib/sdam/topology.js.map +1 -1
- package/lib/utils.js +64 -7
- package/lib/utils.js.map +1 -1
- package/mongodb.d.ts +55 -13
- package/package.json +1 -1
- package/src/client-side-encryption/auto_encrypter.ts +12 -8
- package/src/client-side-encryption/client_encryption.ts +6 -4
- package/src/client-side-encryption/state_machine.ts +80 -36
- package/src/cmap/connection.ts +37 -29
- package/src/cmap/connection_pool.ts +17 -3
- package/src/cmap/wire_protocol/on_data.ts +9 -2
- package/src/collection.ts +15 -8
- package/src/cursor/abstract_cursor.ts +71 -23
- package/src/cursor/aggregation_cursor.ts +5 -3
- package/src/cursor/find_cursor.ts +5 -3
- package/src/cursor/list_collections_cursor.ts +5 -3
- package/src/db.ts +11 -7
- package/src/index.ts +1 -0
- package/src/mongo_client.ts +13 -0
- package/src/mongo_types.ts +38 -0
- package/src/operations/execute_operation.ts +9 -4
- package/src/operations/list_collections.ts +4 -1
- package/src/operations/operation.ts +3 -2
- package/src/sdam/server.ts +31 -18
- package/src/sdam/topology.ts +10 -2
- package/src/utils.ts +79 -6
|
@@ -15,8 +15,15 @@ import { CursorTimeoutContext } from '../cursor/abstract_cursor';
|
|
|
15
15
|
import { getSocks, type SocksLib } from '../deps';
|
|
16
16
|
import { MongoOperationTimeoutError } from '../error';
|
|
17
17
|
import { type MongoClient, type MongoClientOptions } from '../mongo_client';
|
|
18
|
+
import { type Abortable } from '../mongo_types';
|
|
18
19
|
import { Timeout, type TimeoutContext, TimeoutError } from '../timeout';
|
|
19
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
addAbortListener,
|
|
22
|
+
BufferPool,
|
|
23
|
+
kDispose,
|
|
24
|
+
MongoDBCollectionNamespace,
|
|
25
|
+
promiseWithResolvers
|
|
26
|
+
} from '../utils';
|
|
20
27
|
import { autoSelectSocketOptions, type DataKey } from './client_encryption';
|
|
21
28
|
import { MongoCryptError } from './errors';
|
|
22
29
|
import { type MongocryptdManager } from './mongocryptd_manager';
|
|
@@ -189,7 +196,7 @@ export class StateMachine {
|
|
|
189
196
|
async execute(
|
|
190
197
|
executor: StateMachineExecutable,
|
|
191
198
|
context: MongoCryptContext,
|
|
192
|
-
timeoutContext?: TimeoutContext
|
|
199
|
+
options: { timeoutContext?: TimeoutContext } & Abortable
|
|
193
200
|
): Promise<Uint8Array> {
|
|
194
201
|
const keyVaultNamespace = executor._keyVaultNamespace;
|
|
195
202
|
const keyVaultClient = executor._keyVaultClient;
|
|
@@ -199,6 +206,7 @@ export class StateMachine {
|
|
|
199
206
|
let result: Uint8Array | null = null;
|
|
200
207
|
|
|
201
208
|
while (context.state !== MONGOCRYPT_CTX_DONE && context.state !== MONGOCRYPT_CTX_ERROR) {
|
|
209
|
+
options.signal?.throwIfAborted();
|
|
202
210
|
debug(`[context#${context.id}] ${stateToString.get(context.state) || context.state}`);
|
|
203
211
|
|
|
204
212
|
switch (context.state) {
|
|
@@ -214,7 +222,7 @@ export class StateMachine {
|
|
|
214
222
|
metaDataClient,
|
|
215
223
|
context.ns,
|
|
216
224
|
filter,
|
|
217
|
-
|
|
225
|
+
options
|
|
218
226
|
);
|
|
219
227
|
if (collInfo) {
|
|
220
228
|
context.addMongoOperationResponse(collInfo);
|
|
@@ -235,9 +243,9 @@ export class StateMachine {
|
|
|
235
243
|
// When we are using the shared library, we don't have a mongocryptd manager.
|
|
236
244
|
const markedCommand: Uint8Array = mongocryptdManager
|
|
237
245
|
? await mongocryptdManager.withRespawn(
|
|
238
|
-
this.markCommand.bind(this, mongocryptdClient, context.ns, command,
|
|
246
|
+
this.markCommand.bind(this, mongocryptdClient, context.ns, command, options)
|
|
239
247
|
)
|
|
240
|
-
: await this.markCommand(mongocryptdClient, context.ns, command,
|
|
248
|
+
: await this.markCommand(mongocryptdClient, context.ns, command, options);
|
|
241
249
|
|
|
242
250
|
context.addMongoOperationResponse(markedCommand);
|
|
243
251
|
context.finishMongoOperation();
|
|
@@ -246,12 +254,7 @@ export class StateMachine {
|
|
|
246
254
|
|
|
247
255
|
case MONGOCRYPT_CTX_NEED_MONGO_KEYS: {
|
|
248
256
|
const filter = context.nextMongoOperation();
|
|
249
|
-
const keys = await this.fetchKeys(
|
|
250
|
-
keyVaultClient,
|
|
251
|
-
keyVaultNamespace,
|
|
252
|
-
filter,
|
|
253
|
-
timeoutContext
|
|
254
|
-
);
|
|
257
|
+
const keys = await this.fetchKeys(keyVaultClient, keyVaultNamespace, filter, options);
|
|
255
258
|
|
|
256
259
|
if (keys.length === 0) {
|
|
257
260
|
// See docs on EMPTY_V
|
|
@@ -273,7 +276,7 @@ export class StateMachine {
|
|
|
273
276
|
}
|
|
274
277
|
|
|
275
278
|
case MONGOCRYPT_CTX_NEED_KMS: {
|
|
276
|
-
await Promise.all(this.requests(context,
|
|
279
|
+
await Promise.all(this.requests(context, options));
|
|
277
280
|
context.finishKMSRequests();
|
|
278
281
|
break;
|
|
279
282
|
}
|
|
@@ -315,11 +318,13 @@ export class StateMachine {
|
|
|
315
318
|
* @param kmsContext - A C++ KMS context returned from the bindings
|
|
316
319
|
* @returns A promise that resolves when the KMS reply has be fully parsed
|
|
317
320
|
*/
|
|
318
|
-
async kmsRequest(
|
|
321
|
+
async kmsRequest(
|
|
322
|
+
request: MongoCryptKMSRequest,
|
|
323
|
+
options?: { timeoutContext?: TimeoutContext } & Abortable
|
|
324
|
+
): Promise<void> {
|
|
319
325
|
const parsedUrl = request.endpoint.split(':');
|
|
320
326
|
const port = parsedUrl[1] != null ? Number.parseInt(parsedUrl[1], 10) : HTTPS_PORT;
|
|
321
|
-
const socketOptions
|
|
322
|
-
const options: tls.ConnectionOptions & {
|
|
327
|
+
const socketOptions: tls.ConnectionOptions & {
|
|
323
328
|
host: string;
|
|
324
329
|
port: number;
|
|
325
330
|
autoSelectFamily?: boolean;
|
|
@@ -328,7 +333,7 @@ export class StateMachine {
|
|
|
328
333
|
host: parsedUrl[0],
|
|
329
334
|
servername: parsedUrl[0],
|
|
330
335
|
port,
|
|
331
|
-
...socketOptions
|
|
336
|
+
...autoSelectSocketOptions(this.options.socketOptions || {})
|
|
332
337
|
};
|
|
333
338
|
const message = request.message;
|
|
334
339
|
const buffer = new BufferPool();
|
|
@@ -363,7 +368,7 @@ export class StateMachine {
|
|
|
363
368
|
throw error;
|
|
364
369
|
}
|
|
365
370
|
try {
|
|
366
|
-
await this.setTlsOptions(providerTlsOptions,
|
|
371
|
+
await this.setTlsOptions(providerTlsOptions, socketOptions);
|
|
367
372
|
} catch (err) {
|
|
368
373
|
throw onerror(err);
|
|
369
374
|
}
|
|
@@ -380,23 +385,25 @@ export class StateMachine {
|
|
|
380
385
|
.once('close', () => rejectOnNetSocketError(onclose()))
|
|
381
386
|
.once('connect', () => resolveOnNetSocketConnect());
|
|
382
387
|
|
|
388
|
+
let abortListener;
|
|
389
|
+
|
|
383
390
|
try {
|
|
384
391
|
if (this.options.proxyOptions && this.options.proxyOptions.proxyHost) {
|
|
385
392
|
const netSocketOptions = {
|
|
393
|
+
...socketOptions,
|
|
386
394
|
host: this.options.proxyOptions.proxyHost,
|
|
387
|
-
port: this.options.proxyOptions.proxyPort || 1080
|
|
388
|
-
...socketOptions
|
|
395
|
+
port: this.options.proxyOptions.proxyPort || 1080
|
|
389
396
|
};
|
|
390
397
|
netSocket.connect(netSocketOptions);
|
|
391
398
|
await willConnect;
|
|
392
399
|
|
|
393
400
|
try {
|
|
394
401
|
socks ??= loadSocks();
|
|
395
|
-
|
|
402
|
+
socketOptions.socket = (
|
|
396
403
|
await socks.SocksClient.createConnection({
|
|
397
404
|
existing_socket: netSocket,
|
|
398
405
|
command: 'connect',
|
|
399
|
-
destination: { host:
|
|
406
|
+
destination: { host: socketOptions.host, port: socketOptions.port },
|
|
400
407
|
proxy: {
|
|
401
408
|
// host and port are ignored because we pass existing_socket
|
|
402
409
|
host: 'iLoveJavaScript',
|
|
@@ -412,7 +419,7 @@ export class StateMachine {
|
|
|
412
419
|
}
|
|
413
420
|
}
|
|
414
421
|
|
|
415
|
-
socket = tls.connect(
|
|
422
|
+
socket = tls.connect(socketOptions, () => {
|
|
416
423
|
socket.write(message);
|
|
417
424
|
});
|
|
418
425
|
|
|
@@ -422,6 +429,11 @@ export class StateMachine {
|
|
|
422
429
|
resolve
|
|
423
430
|
} = promiseWithResolvers<void>();
|
|
424
431
|
|
|
432
|
+
abortListener = addAbortListener(options?.signal, function () {
|
|
433
|
+
destroySockets();
|
|
434
|
+
rejectOnTlsSocketError(this.reason);
|
|
435
|
+
});
|
|
436
|
+
|
|
425
437
|
socket
|
|
426
438
|
.once('error', err => rejectOnTlsSocketError(onerror(err)))
|
|
427
439
|
.once('close', () => rejectOnTlsSocketError(onclose()))
|
|
@@ -436,8 +448,11 @@ export class StateMachine {
|
|
|
436
448
|
resolve();
|
|
437
449
|
}
|
|
438
450
|
});
|
|
439
|
-
await (timeoutContext?.csotEnabled()
|
|
440
|
-
? Promise.all([
|
|
451
|
+
await (options?.timeoutContext?.csotEnabled()
|
|
452
|
+
? Promise.all([
|
|
453
|
+
willResolveKmsRequest,
|
|
454
|
+
Timeout.expires(options.timeoutContext?.remainingTimeMS)
|
|
455
|
+
])
|
|
441
456
|
: willResolveKmsRequest);
|
|
442
457
|
} catch (error) {
|
|
443
458
|
if (error instanceof TimeoutError)
|
|
@@ -446,16 +461,17 @@ export class StateMachine {
|
|
|
446
461
|
} finally {
|
|
447
462
|
// There's no need for any more activity on this socket at this point.
|
|
448
463
|
destroySockets();
|
|
464
|
+
abortListener?.[kDispose]();
|
|
449
465
|
}
|
|
450
466
|
}
|
|
451
467
|
|
|
452
|
-
*requests(context: MongoCryptContext, timeoutContext?: TimeoutContext) {
|
|
468
|
+
*requests(context: MongoCryptContext, options?: { timeoutContext?: TimeoutContext } & Abortable) {
|
|
453
469
|
for (
|
|
454
470
|
let request = context.nextKMSRequest();
|
|
455
471
|
request != null;
|
|
456
472
|
request = context.nextKMSRequest()
|
|
457
473
|
) {
|
|
458
|
-
yield this.kmsRequest(request,
|
|
474
|
+
yield this.kmsRequest(request, options);
|
|
459
475
|
}
|
|
460
476
|
}
|
|
461
477
|
|
|
@@ -516,14 +532,16 @@ export class StateMachine {
|
|
|
516
532
|
client: MongoClient,
|
|
517
533
|
ns: string,
|
|
518
534
|
filter: Document,
|
|
519
|
-
timeoutContext?: TimeoutContext
|
|
535
|
+
options?: { timeoutContext?: TimeoutContext } & Abortable
|
|
520
536
|
): Promise<Uint8Array | null> {
|
|
521
537
|
const { db } = MongoDBCollectionNamespace.fromString(ns);
|
|
522
538
|
|
|
523
539
|
const cursor = client.db(db).listCollections(filter, {
|
|
524
540
|
promoteLongs: false,
|
|
525
541
|
promoteValues: false,
|
|
526
|
-
timeoutContext:
|
|
542
|
+
timeoutContext:
|
|
543
|
+
options?.timeoutContext && new CursorTimeoutContext(options?.timeoutContext, Symbol()),
|
|
544
|
+
signal: options?.signal
|
|
527
545
|
});
|
|
528
546
|
|
|
529
547
|
// There is always exactly zero or one matching documents, so this should always exhaust the cursor
|
|
@@ -547,17 +565,30 @@ export class StateMachine {
|
|
|
547
565
|
client: MongoClient,
|
|
548
566
|
ns: string,
|
|
549
567
|
command: Uint8Array,
|
|
550
|
-
timeoutContext?: TimeoutContext
|
|
568
|
+
options?: { timeoutContext?: TimeoutContext } & Abortable
|
|
551
569
|
): Promise<Uint8Array> {
|
|
552
570
|
const { db } = MongoDBCollectionNamespace.fromString(ns);
|
|
553
571
|
const bsonOptions = { promoteLongs: false, promoteValues: false };
|
|
554
572
|
const rawCommand = deserialize(command, bsonOptions);
|
|
555
573
|
|
|
574
|
+
const commandOptions: {
|
|
575
|
+
timeoutMS?: number;
|
|
576
|
+
signal?: AbortSignal;
|
|
577
|
+
} = {
|
|
578
|
+
timeoutMS: undefined,
|
|
579
|
+
signal: undefined
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
if (options?.timeoutContext?.csotEnabled()) {
|
|
583
|
+
commandOptions.timeoutMS = options.timeoutContext.remainingTimeMS;
|
|
584
|
+
}
|
|
585
|
+
if (options?.signal) {
|
|
586
|
+
commandOptions.signal = options.signal;
|
|
587
|
+
}
|
|
588
|
+
|
|
556
589
|
const response = await client.db(db).command(rawCommand, {
|
|
557
590
|
...bsonOptions,
|
|
558
|
-
...
|
|
559
|
-
? { timeoutMS: timeoutContext?.remainingTimeMS }
|
|
560
|
-
: undefined)
|
|
591
|
+
...commandOptions
|
|
561
592
|
});
|
|
562
593
|
|
|
563
594
|
return serialize(response, this.bsonOptions);
|
|
@@ -575,17 +606,30 @@ export class StateMachine {
|
|
|
575
606
|
client: MongoClient,
|
|
576
607
|
keyVaultNamespace: string,
|
|
577
608
|
filter: Uint8Array,
|
|
578
|
-
timeoutContext?: TimeoutContext
|
|
609
|
+
options?: { timeoutContext?: TimeoutContext } & Abortable
|
|
579
610
|
): Promise<Array<DataKey>> {
|
|
580
611
|
const { db: dbName, collection: collectionName } =
|
|
581
612
|
MongoDBCollectionNamespace.fromString(keyVaultNamespace);
|
|
582
613
|
|
|
614
|
+
const commandOptions: {
|
|
615
|
+
timeoutContext?: CursorTimeoutContext;
|
|
616
|
+
signal?: AbortSignal;
|
|
617
|
+
} = {
|
|
618
|
+
timeoutContext: undefined,
|
|
619
|
+
signal: undefined
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
if (options?.timeoutContext != null) {
|
|
623
|
+
commandOptions.timeoutContext = new CursorTimeoutContext(options.timeoutContext, Symbol());
|
|
624
|
+
}
|
|
625
|
+
if (options?.signal != null) {
|
|
626
|
+
commandOptions.signal = options.signal;
|
|
627
|
+
}
|
|
628
|
+
|
|
583
629
|
return client
|
|
584
630
|
.db(dbName)
|
|
585
631
|
.collection<DataKey>(collectionName, { readConcern: { level: 'majority' } })
|
|
586
|
-
.find(deserialize(filter),
|
|
587
|
-
timeoutContext: timeoutContext && new CursorTimeoutContext(timeoutContext, Symbol())
|
|
588
|
-
})
|
|
632
|
+
.find(deserialize(filter), commandOptions)
|
|
589
633
|
.toArray();
|
|
590
634
|
}
|
|
591
635
|
}
|
package/src/cmap/connection.ts
CHANGED
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
import type { ServerApi, SupportedNodeConnectionOptions } from '../mongo_client';
|
|
34
34
|
import { type MongoClientAuthProviders } from '../mongo_client_auth_providers';
|
|
35
35
|
import { MongoLoggableComponent, type MongoLogger, SeverityLevel } from '../mongo_logger';
|
|
36
|
-
import { type CancellationToken, TypedEventEmitter } from '../mongo_types';
|
|
36
|
+
import { type Abortable, type CancellationToken, TypedEventEmitter } from '../mongo_types';
|
|
37
37
|
import { ReadPreference, type ReadPreferenceLike } from '../read_preference';
|
|
38
38
|
import { ServerType } from '../sdam/common';
|
|
39
39
|
import { applySession, type ClientSession, updateSessionFromResponse } from '../sessions';
|
|
@@ -438,7 +438,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
|
|
|
438
438
|
|
|
439
439
|
private async *sendWire(
|
|
440
440
|
message: WriteProtocolMessageType,
|
|
441
|
-
options: CommandOptions,
|
|
441
|
+
options: CommandOptions & Abortable,
|
|
442
442
|
responseType?: MongoDBResponseConstructor
|
|
443
443
|
): AsyncGenerator<MongoDBResponse> {
|
|
444
444
|
this.throwIfAborted();
|
|
@@ -453,7 +453,8 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
|
|
|
453
453
|
await this.writeCommand(message, {
|
|
454
454
|
agreedCompressor: this.description.compressor ?? 'none',
|
|
455
455
|
zlibCompressionLevel: this.description.zlibCompressionLevel,
|
|
456
|
-
timeoutContext: options.timeoutContext
|
|
456
|
+
timeoutContext: options.timeoutContext,
|
|
457
|
+
signal: options.signal
|
|
457
458
|
});
|
|
458
459
|
|
|
459
460
|
if (options.noResponse || message.moreToCome) {
|
|
@@ -473,7 +474,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
|
|
|
473
474
|
);
|
|
474
475
|
}
|
|
475
476
|
|
|
476
|
-
for await (const response of this.readMany(
|
|
477
|
+
for await (const response of this.readMany(options)) {
|
|
477
478
|
this.socket.setTimeout(0);
|
|
478
479
|
const bson = response.parse();
|
|
479
480
|
|
|
@@ -492,9 +493,11 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
|
|
|
492
493
|
private async *sendCommand(
|
|
493
494
|
ns: MongoDBNamespace,
|
|
494
495
|
command: Document,
|
|
495
|
-
options: CommandOptions,
|
|
496
|
+
options: CommandOptions & Abortable,
|
|
496
497
|
responseType?: MongoDBResponseConstructor
|
|
497
498
|
) {
|
|
499
|
+
options?.signal?.throwIfAborted();
|
|
500
|
+
|
|
498
501
|
const message = this.prepareCommand(ns.db, command, options);
|
|
499
502
|
let started = 0;
|
|
500
503
|
if (this.shouldEmitAndLogCommand) {
|
|
@@ -610,10 +613,12 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
|
|
|
610
613
|
public async command(
|
|
611
614
|
ns: MongoDBNamespace,
|
|
612
615
|
command: Document,
|
|
613
|
-
options: CommandOptions = {},
|
|
616
|
+
options: CommandOptions & Abortable = {},
|
|
614
617
|
responseType?: MongoDBResponseConstructor
|
|
615
618
|
): Promise<Document> {
|
|
616
619
|
this.throwIfAborted();
|
|
620
|
+
options.signal?.throwIfAborted();
|
|
621
|
+
|
|
617
622
|
for await (const document of this.sendCommand(ns, command, options, responseType)) {
|
|
618
623
|
if (options.timeoutContext?.csotEnabled()) {
|
|
619
624
|
if (MongoDBResponse.is(document)) {
|
|
@@ -676,7 +681,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
|
|
|
676
681
|
agreedCompressor?: CompressorName;
|
|
677
682
|
zlibCompressionLevel?: number;
|
|
678
683
|
timeoutContext?: TimeoutContext;
|
|
679
|
-
}
|
|
684
|
+
} & Abortable
|
|
680
685
|
): Promise<void> {
|
|
681
686
|
const finalCommand =
|
|
682
687
|
options.agreedCompressor === 'none' || !OpCompressedRequest.canCompress(command)
|
|
@@ -701,23 +706,23 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
|
|
|
701
706
|
|
|
702
707
|
if (this.socket.write(buffer)) return;
|
|
703
708
|
|
|
704
|
-
const drainEvent = once<void>(this.socket, 'drain');
|
|
709
|
+
const drainEvent = once<void>(this.socket, 'drain', options);
|
|
705
710
|
const timeout = options?.timeoutContext?.timeoutForSocketWrite;
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
} finally {
|
|
717
|
-
timeout.clear();
|
|
711
|
+
const drained = timeout ? Promise.race([drainEvent, timeout]) : drainEvent;
|
|
712
|
+
try {
|
|
713
|
+
return await drained;
|
|
714
|
+
} catch (writeError) {
|
|
715
|
+
if (TimeoutError.is(writeError)) {
|
|
716
|
+
const timeoutError = new MongoOperationTimeoutError('Timed out at socket write');
|
|
717
|
+
this.onError(timeoutError);
|
|
718
|
+
throw timeoutError;
|
|
719
|
+
} else if (writeError === options.signal?.reason) {
|
|
720
|
+
this.onError(writeError);
|
|
718
721
|
}
|
|
722
|
+
throw writeError;
|
|
723
|
+
} finally {
|
|
724
|
+
timeout?.clear();
|
|
719
725
|
}
|
|
720
|
-
return await drainEvent;
|
|
721
726
|
}
|
|
722
727
|
|
|
723
728
|
/**
|
|
@@ -729,9 +734,11 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
|
|
|
729
734
|
*
|
|
730
735
|
* Note that `for-await` loops call `return` automatically when the loop is exited.
|
|
731
736
|
*/
|
|
732
|
-
private async *readMany(
|
|
733
|
-
|
|
734
|
-
|
|
737
|
+
private async *readMany(
|
|
738
|
+
options: {
|
|
739
|
+
timeoutContext?: TimeoutContext;
|
|
740
|
+
} & Abortable
|
|
741
|
+
): AsyncGenerator<OpMsgResponse | OpReply> {
|
|
735
742
|
try {
|
|
736
743
|
this.dataEvents = onData(this.messageStream, options);
|
|
737
744
|
this.messageStream.resume();
|
|
@@ -745,16 +752,17 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
|
|
|
745
752
|
}
|
|
746
753
|
}
|
|
747
754
|
} catch (readError) {
|
|
748
|
-
const err = readError;
|
|
749
755
|
if (TimeoutError.is(readError)) {
|
|
750
|
-
const
|
|
756
|
+
const timeoutError = new MongoOperationTimeoutError(
|
|
751
757
|
`Timed out during socket read (${readError.duration}ms)`
|
|
752
758
|
);
|
|
753
759
|
this.dataEvents = null;
|
|
754
|
-
this.onError(
|
|
755
|
-
throw
|
|
760
|
+
this.onError(timeoutError);
|
|
761
|
+
throw timeoutError;
|
|
762
|
+
} else if (readError === options.signal?.reason) {
|
|
763
|
+
this.onError(readError);
|
|
756
764
|
}
|
|
757
|
-
throw
|
|
765
|
+
throw readError;
|
|
758
766
|
} finally {
|
|
759
767
|
this.dataEvents = null;
|
|
760
768
|
this.messageStream.pause();
|
|
@@ -25,10 +25,18 @@ import {
|
|
|
25
25
|
MongoRuntimeError,
|
|
26
26
|
MongoServerError
|
|
27
27
|
} from '../error';
|
|
28
|
-
import { CancellationToken, TypedEventEmitter } from '../mongo_types';
|
|
28
|
+
import { type Abortable, CancellationToken, TypedEventEmitter } from '../mongo_types';
|
|
29
29
|
import type { Server } from '../sdam/server';
|
|
30
30
|
import { type TimeoutContext, TimeoutError } from '../timeout';
|
|
31
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
addAbortListener,
|
|
33
|
+
type Callback,
|
|
34
|
+
kDispose,
|
|
35
|
+
List,
|
|
36
|
+
makeCounter,
|
|
37
|
+
now,
|
|
38
|
+
promiseWithResolvers
|
|
39
|
+
} from '../utils';
|
|
32
40
|
import { connect } from './connect';
|
|
33
41
|
import { Connection, type ConnectionEvents, type ConnectionOptions } from './connection';
|
|
34
42
|
import {
|
|
@@ -316,7 +324,7 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
|
|
|
316
324
|
* will be held by the pool. This means that if a connection is checked out it MUST be checked back in or
|
|
317
325
|
* explicitly destroyed by the new owner.
|
|
318
326
|
*/
|
|
319
|
-
async checkOut(options: { timeoutContext: TimeoutContext }): Promise<Connection> {
|
|
327
|
+
async checkOut(options: { timeoutContext: TimeoutContext } & Abortable): Promise<Connection> {
|
|
320
328
|
const checkoutTime = now();
|
|
321
329
|
this.emitAndLog(
|
|
322
330
|
ConnectionPool.CONNECTION_CHECK_OUT_STARTED,
|
|
@@ -334,6 +342,11 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
|
|
|
334
342
|
checkoutTime
|
|
335
343
|
};
|
|
336
344
|
|
|
345
|
+
const abortListener = addAbortListener(options.signal, function () {
|
|
346
|
+
waitQueueMember.cancelled = true;
|
|
347
|
+
reject(this.reason);
|
|
348
|
+
});
|
|
349
|
+
|
|
337
350
|
this.waitQueue.push(waitQueueMember);
|
|
338
351
|
process.nextTick(() => this.processWaitQueue());
|
|
339
352
|
|
|
@@ -364,6 +377,7 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
|
|
|
364
377
|
}
|
|
365
378
|
throw error;
|
|
366
379
|
} finally {
|
|
380
|
+
abortListener?.[kDispose]();
|
|
367
381
|
timeout?.clear();
|
|
368
382
|
}
|
|
369
383
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type EventEmitter } from 'events';
|
|
2
2
|
|
|
3
|
+
import { type Abortable } from '../../mongo_types';
|
|
3
4
|
import { type TimeoutContext } from '../../timeout';
|
|
4
|
-
import { List, promiseWithResolvers } from '../../utils';
|
|
5
|
+
import { addAbortListener, kDispose, List, promiseWithResolvers } from '../../utils';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* @internal
|
|
@@ -21,8 +22,10 @@ type PendingPromises = Omit<
|
|
|
21
22
|
*/
|
|
22
23
|
export function onData(
|
|
23
24
|
emitter: EventEmitter,
|
|
24
|
-
{ timeoutContext }: { timeoutContext?: TimeoutContext }
|
|
25
|
+
{ timeoutContext, signal }: { timeoutContext?: TimeoutContext } & Abortable
|
|
25
26
|
) {
|
|
27
|
+
signal?.throwIfAborted();
|
|
28
|
+
|
|
26
29
|
// Setup pending events and pending promise lists
|
|
27
30
|
/**
|
|
28
31
|
* When the caller has not yet called .next(), we store the
|
|
@@ -90,6 +93,9 @@ export function onData(
|
|
|
90
93
|
// Adding event handlers
|
|
91
94
|
emitter.on('data', eventHandler);
|
|
92
95
|
emitter.on('error', errorHandler);
|
|
96
|
+
const abortListener = addAbortListener(signal, function () {
|
|
97
|
+
errorHandler(this.reason);
|
|
98
|
+
});
|
|
93
99
|
|
|
94
100
|
const timeoutForSocketRead = timeoutContext?.timeoutForSocketRead;
|
|
95
101
|
timeoutForSocketRead?.throwIfExpired();
|
|
@@ -115,6 +121,7 @@ export function onData(
|
|
|
115
121
|
// Adding event handlers
|
|
116
122
|
emitter.off('data', eventHandler);
|
|
117
123
|
emitter.off('error', errorHandler);
|
|
124
|
+
abortListener?.[kDispose]();
|
|
118
125
|
finished = true;
|
|
119
126
|
timeoutForSocketRead?.clear();
|
|
120
127
|
const doneResult = { value: undefined, done: finished } as const;
|
package/src/collection.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type { Db } from './db';
|
|
|
14
14
|
import { MongoInvalidArgumentError, MongoOperationTimeoutError } from './error';
|
|
15
15
|
import type { MongoClient, PkFactory } from './mongo_client';
|
|
16
16
|
import type {
|
|
17
|
+
Abortable,
|
|
17
18
|
Filter,
|
|
18
19
|
Flatten,
|
|
19
20
|
OptionalUnlessRequiredId,
|
|
@@ -505,7 +506,7 @@ export class Collection<TSchema extends Document = Document> {
|
|
|
505
506
|
async findOne(filter: Filter<TSchema>): Promise<WithId<TSchema> | null>;
|
|
506
507
|
async findOne(
|
|
507
508
|
filter: Filter<TSchema>,
|
|
508
|
-
options: Omit<FindOptions, 'timeoutMode'>
|
|
509
|
+
options: Omit<FindOptions, 'timeoutMode'> & Abortable
|
|
509
510
|
): Promise<WithId<TSchema> | null>;
|
|
510
511
|
|
|
511
512
|
// allow an override of the schema.
|
|
@@ -513,12 +514,12 @@ export class Collection<TSchema extends Document = Document> {
|
|
|
513
514
|
async findOne<T = TSchema>(filter: Filter<TSchema>): Promise<T | null>;
|
|
514
515
|
async findOne<T = TSchema>(
|
|
515
516
|
filter: Filter<TSchema>,
|
|
516
|
-
options?: Omit<FindOptions, 'timeoutMode'>
|
|
517
|
+
options?: Omit<FindOptions, 'timeoutMode'> & Abortable
|
|
517
518
|
): Promise<T | null>;
|
|
518
519
|
|
|
519
520
|
async findOne(
|
|
520
521
|
filter: Filter<TSchema> = {},
|
|
521
|
-
options: FindOptions = {}
|
|
522
|
+
options: FindOptions & Abortable = {}
|
|
522
523
|
): Promise<WithId<TSchema> | null> {
|
|
523
524
|
const cursor = this.find(filter, options).limit(-1).batchSize(1);
|
|
524
525
|
const res = await cursor.next();
|
|
@@ -532,9 +533,15 @@ export class Collection<TSchema extends Document = Document> {
|
|
|
532
533
|
* @param filter - The filter predicate. If unspecified, then all documents in the collection will match the predicate
|
|
533
534
|
*/
|
|
534
535
|
find(): FindCursor<WithId<TSchema>>;
|
|
535
|
-
find(filter: Filter<TSchema>, options?: FindOptions): FindCursor<WithId<TSchema>>;
|
|
536
|
-
find<T extends Document>(
|
|
537
|
-
|
|
536
|
+
find(filter: Filter<TSchema>, options?: FindOptions & Abortable): FindCursor<WithId<TSchema>>;
|
|
537
|
+
find<T extends Document>(
|
|
538
|
+
filter: Filter<TSchema>,
|
|
539
|
+
options?: FindOptions & Abortable
|
|
540
|
+
): FindCursor<T>;
|
|
541
|
+
find(
|
|
542
|
+
filter: Filter<TSchema> = {},
|
|
543
|
+
options: FindOptions & Abortable = {}
|
|
544
|
+
): FindCursor<WithId<TSchema>> {
|
|
538
545
|
return new FindCursor<WithId<TSchema>>(
|
|
539
546
|
this.client,
|
|
540
547
|
this.s.namespace,
|
|
@@ -792,7 +799,7 @@ export class Collection<TSchema extends Document = Document> {
|
|
|
792
799
|
*/
|
|
793
800
|
async countDocuments(
|
|
794
801
|
filter: Filter<TSchema> = {},
|
|
795
|
-
options: CountDocumentsOptions = {}
|
|
802
|
+
options: CountDocumentsOptions & Abortable = {}
|
|
796
803
|
): Promise<number> {
|
|
797
804
|
const pipeline = [];
|
|
798
805
|
pipeline.push({ $match: filter });
|
|
@@ -1006,7 +1013,7 @@ export class Collection<TSchema extends Document = Document> {
|
|
|
1006
1013
|
*/
|
|
1007
1014
|
aggregate<T extends Document = Document>(
|
|
1008
1015
|
pipeline: Document[] = [],
|
|
1009
|
-
options?: AggregateOptions
|
|
1016
|
+
options?: AggregateOptions & Abortable
|
|
1010
1017
|
): AggregationCursor<T> {
|
|
1011
1018
|
if (!Array.isArray(pipeline)) {
|
|
1012
1019
|
throw new MongoInvalidArgumentError(
|