1
- import { on } from 'stream' ;
1
+ import { type Readable , Transform , type TransformCallback } from 'stream' ;
2
2
import { clearTimeout , setTimeout } from 'timers' ;
3
3
import { promisify } from 'util' ;
4
4
@@ -61,6 +61,7 @@ import type { ClientMetadata } from './handshake/client_metadata';
61
61
import { MessageStream , type OperationDescription } from './message_stream' ;
62
62
import { StreamDescription , type StreamDescriptionOptions } from './stream_description' ;
63
63
import { decompressResponse } from './wire_protocol/compression' ;
64
+ import { onData } from './wire_protocol/on_data' ;
64
65
import { getReadPreference , isSharded } from './wire_protocol/shared' ;
65
66
66
67
/** @internal */
@@ -807,17 +808,19 @@ export class ModernConnection extends TypedEventEmitter<ConnectionEvents> {
807
808
/** @internal */
808
809
authContext ?: AuthContext ;
809
810
810
- /**@internal */
811
811
delayedTimeoutId : NodeJS . Timeout | null = null ;
812
812
/** @internal */
813
813
[ kDescription ] : StreamDescription ;
814
814
/** @internal */
815
815
[ kGeneration ] : number ;
816
816
/** @internal */
817
817
[ kLastUseTime ] : number ;
818
- /** @internal */
819
- socket : Stream ;
820
- controller : AbortController ;
818
+
819
+ private socket : Stream ;
820
+ private controller : AbortController ;
821
+ private messageStream : Readable ;
822
+ private socketWrite : ( buffer : Uint8Array ) => Promise < void > ;
823
+
821
824
/** @internal */
822
825
[ kHello ] : Document | null ;
823
826
/** @internal */
@@ -857,9 +860,18 @@ export class ModernConnection extends TypedEventEmitter<ConnectionEvents> {
857
860
858
861
this . socket = stream ;
859
862
this . controller = new AbortController ( ) ;
860
- this . socket . on ( 'error' , this . onError . bind ( this ) ) ;
863
+
864
+ this . messageStream = this . socket
865
+ . on ( 'error' , this . onError . bind ( this ) )
866
+ . pipe ( new SizedMessageTransform ( { connection : this } ) )
867
+ . on ( 'error' , this . onError . bind ( this ) ) ;
861
868
this . socket . on ( 'close' , this . onClose . bind ( this ) ) ;
862
869
this . socket . on ( 'timeout' , this . onTimeout . bind ( this ) ) ;
870
+
871
+ const socketWrite = promisify ( this . socket . write . bind ( this . socket ) ) ;
872
+ this . socketWrite = async buffer => {
873
+ return abortable ( socketWrite ( buffer ) , { signal : this . controller . signal } ) ;
874
+ } ;
863
875
}
864
876
865
877
async commandAsync ( ...args : Parameters < typeof this . command > ) {
@@ -1060,23 +1072,19 @@ export class ModernConnection extends TypedEventEmitter<ConnectionEvents> {
1060
1072
}
1061
1073
1062
1074
try {
1063
- await writeCommand ( this , message , {
1075
+ await this . writeCommand ( message , {
1064
1076
agreedCompressor : this . description . compressor ?? 'none' ,
1065
- zlibCompressionLevel : this . description . zlibCompressionLevel ,
1066
- signal : this . controller . signal
1077
+ zlibCompressionLevel : this . description . zlibCompressionLevel
1067
1078
} ) ;
1068
1079
1069
- // TODO(NODE-5770): Replace controller to avoid boundless 'abort' listeners
1070
- this . controller = new AbortController ( ) ;
1071
-
1072
1080
if ( options . noResponse ) {
1073
1081
yield { ok : 1 } ;
1074
1082
return ;
1075
1083
}
1076
1084
1077
1085
this . controller . signal . throwIfAborted ( ) ;
1078
1086
1079
- for await ( const response of readMany ( this , { signal : this . controller . signal } ) ) {
1087
+ for await ( const response of this . readMany ( ) ) {
1080
1088
this . socket . setTimeout ( 0 ) ;
1081
1089
response . parse ( options ) ;
1082
1090
@@ -1094,9 +1102,6 @@ export class ModernConnection extends TypedEventEmitter<ConnectionEvents> {
1094
1102
}
1095
1103
}
1096
1104
1097
- // TODO(NODE-5770): Replace controller to avoid boundless 'abort' listeners
1098
- this . controller = new AbortController ( ) ;
1099
-
1100
1105
yield document ;
1101
1106
this . controller . signal . throwIfAborted ( ) ;
1102
1107
@@ -1214,121 +1219,83 @@ export class ModernConnection extends TypedEventEmitter<ConnectionEvents> {
1214
1219
} ;
1215
1220
exhaustLoop ( ) . catch ( replyListener ) ;
1216
1221
}
1217
- }
1218
1222
1219
- const kDefaultMaxBsonMessageSize = 1024 * 1024 * 16 * 4 ;
1220
-
1221
- /**
1222
- * @internal
1223
- *
1224
- * This helper reads chucks of data out of a socket and buffers them until it has received a
1225
- * full wire protocol message.
1226
- *
1227
- * By itself, produces an infinite async generator of wire protocol messages and consumers must end
1228
- * the stream by calling `return` on the generator.
1229
- *
1230
- * Note that `for-await` loops call `return` automatically when the loop is exited.
1231
- */
1232
- export async function * readWireProtocolMessages (
1233
- connection : ModernConnection ,
1234
- { signal } : { signal ?: AbortSignal } = { }
1235
- ) : AsyncGenerator < Buffer > {
1236
- const bufferPool = new BufferPool ( ) ;
1237
- const maxBsonMessageSize = connection . hello ?. maxBsonMessageSize ?? kDefaultMaxBsonMessageSize ;
1238
- for await ( const [ chunk ] of on ( connection . socket , 'data' , { signal } ) ) {
1239
- if ( connection . delayedTimeoutId ) {
1240
- clearTimeout ( connection . delayedTimeoutId ) ;
1241
- connection . delayedTimeoutId = null ;
1242
- }
1243
-
1244
- bufferPool . append ( chunk ) ;
1245
- const sizeOfMessage = bufferPool . getInt32 ( ) ;
1223
+ /**
1224
+ * @internal
1225
+ *
1226
+ * Writes an OP_MSG or OP_QUERY request to the socket, optionally compressing the command. This method
1227
+ * waits until the socket's buffer has emptied (the Nodejs socket `drain` event has fired).
1228
+ */
1229
+ async writeCommand (
1230
+ command : WriteProtocolMessageType ,
1231
+ options : Partial < Pick < OperationDescription , 'agreedCompressor' | 'zlibCompressionLevel' > >
1232
+ ) : Promise < void > {
1233
+ const finalCommand =
1234
+ options . agreedCompressor === 'none' || ! OpCompressedRequest . canCompress ( command )
1235
+ ? command
1236
+ : new OpCompressedRequest ( command , {
1237
+ agreedCompressor : options . agreedCompressor ?? 'none' ,
1238
+ zlibCompressionLevel : options . zlibCompressionLevel ?? 0
1239
+ } ) ;
1246
1240
1247
- if ( sizeOfMessage == null ) {
1248
- continue ;
1249
- }
1241
+ const buffer = Buffer . concat ( await finalCommand . toBin ( ) ) ;
1250
1242
1251
- if ( sizeOfMessage < 0 ) {
1252
- throw new MongoParseError ( `Invalid message size: ${ sizeOfMessage } ` ) ;
1253
- }
1243
+ return this . socketWrite ( buffer ) ;
1244
+ }
1254
1245
1255
- if ( sizeOfMessage > maxBsonMessageSize ) {
1256
- throw new MongoParseError (
1257
- `Invalid message size: ${ sizeOfMessage } , max allowed: ${ maxBsonMessageSize } `
1258
- ) ;
1259
- }
1246
+ /**
1247
+ * @internal
1248
+ *
1249
+ * Returns an async generator that yields full wire protocol messages from the underlying socket. This function
1250
+ * yields messages until `moreToCome` is false or not present in a response, or the caller cancels the request
1251
+ * by calling `return` on the generator.
1252
+ *
1253
+ * Note that `for-await` loops call `return` automatically when the loop is exited.
1254
+ */
1255
+ async * readMany ( ) : AsyncGenerator < OpMsgResponse | OpQueryResponse > {
1256
+ for await ( const message of onData ( this . messageStream , { signal : this . controller . signal } ) ) {
1257
+ const response = await decompressResponse ( message ) ;
1258
+ yield response ;
1260
1259
1261
- if ( sizeOfMessage > bufferPool . length ) {
1262
- continue ;
1260
+ if ( ! response . moreToCome ) {
1261
+ return ;
1262
+ }
1263
1263
}
1264
-
1265
- yield bufferPool . read ( sizeOfMessage ) ;
1266
1264
}
1267
1265
}
1268
1266
1269
- /**
1270
- * @internal
1271
- *
1272
- * Writes an OP_MSG or OP_QUERY request to the socket, optionally compressing the command. This method
1273
- * waits until the socket's buffer has emptied (the Nodejs socket `drain` event has fired).
1274
- */
1275
- export async function writeCommand (
1276
- connection : ModernConnection ,
1277
- command : WriteProtocolMessageType ,
1278
- options : Partial < Pick < OperationDescription , 'agreedCompressor' | 'zlibCompressionLevel' > > & {
1279
- signal ?: AbortSignal ;
1280
- }
1281
- ) : Promise < void > {
1282
- const finalCommand =
1283
- options . agreedCompressor === 'none' || ! OpCompressedRequest . canCompress ( command )
1284
- ? command
1285
- : new OpCompressedRequest ( command , {
1286
- agreedCompressor : options . agreedCompressor ?? 'none' ,
1287
- zlibCompressionLevel : options . zlibCompressionLevel ?? 0
1288
- } ) ;
1267
+ /** @internal */
1268
+ export class SizedMessageTransform extends Transform {
1269
+ bufferPool : BufferPool ;
1270
+ connection : ModernConnection ;
1271
+
1272
+ constructor ( { connection } : { connection : ModernConnection } ) {
1273
+ super ( { objectMode : false } ) ;
1274
+ this . bufferPool = new BufferPool ( ) ;
1275
+ this . connection = connection ;
1276
+ }
1277
+ override _transform ( chunk : Buffer , encoding : unknown , callback : TransformCallback ) : void {
1278
+ if ( this . connection . delayedTimeoutId != null ) {
1279
+ clearTimeout ( this . connection . delayedTimeoutId ) ;
1280
+ this . connection . delayedTimeoutId = null ;
1281
+ }
1289
1282
1290
- const buffer = Buffer . concat ( await finalCommand . toBin ( ) ) ;
1283
+ this . bufferPool . append ( chunk ) ;
1284
+ const sizeOfMessage = this . bufferPool . getInt32 ( ) ;
1291
1285
1292
- const socketWriteFn = promisify ( connection . socket . write . bind ( connection . socket ) ) ;
1286
+ if ( sizeOfMessage == null ) {
1287
+ return callback ( ) ;
1288
+ }
1293
1289
1294
- return abortable ( socketWriteFn ( buffer ) , options ) ;
1295
- }
1290
+ if ( sizeOfMessage < 0 ) {
1291
+ return callback ( new MongoParseError ( `Invalid message size: ${ sizeOfMessage } , too small` ) ) ;
1292
+ }
1296
1293
1297
- /**
1298
- * @internal
1299
- *
1300
- * Returns an async generator that yields full wire protocol messages from the underlying socket. This function
1301
- * yields messages until `moreToCome` is false or not present in a response, or the caller cancels the request
1302
- * by calling `return` on the generator.
1303
- *
1304
- * Note that `for-await` loops call `return` automatically when the loop is exited.
1305
- */
1306
- export async function * readMany (
1307
- connection : ModernConnection ,
1308
- options : { signal ?: AbortSignal } = { }
1309
- ) : AsyncGenerator < OpMsgResponse | OpQueryResponse > {
1310
- for await ( const message of readWireProtocolMessages ( connection , options ) ) {
1311
- const response = await decompressResponse ( message ) ;
1312
- yield response ;
1313
-
1314
- if ( ! response . moreToCome ) {
1315
- return ;
1294
+ if ( sizeOfMessage > this . bufferPool . length ) {
1295
+ return callback ( ) ;
1316
1296
}
1317
- }
1318
- }
1319
1297
1320
- /**
1321
- * @internal
1322
- *
1323
- * Reads a single wire protocol message out of a connection.
1324
- */
1325
- export async function read (
1326
- connection : ModernConnection ,
1327
- options : { signal ?: AbortSignal } = { }
1328
- ) : Promise < OpMsgResponse | OpQueryResponse > {
1329
- for await ( const value of readMany ( connection , options ) ) {
1330
- return value ;
1298
+ const message = this . bufferPool . read ( sizeOfMessage ) ;
1299
+ return callback ( null , message ) ;
1331
1300
}
1332
-
1333
- throw new MongoRuntimeError ( 'unable to read message off of connection' ) ;
1334
1301
}
0 commit comments