@@ -7,6 +7,7 @@ import { PassThrough, Writable } from 'stream';
7
7
import { HttpStatus , RequestMethod } from '../../../common' ;
8
8
import { RouterResponseController } from '../../router/router-response-controller' ;
9
9
import { NoopHttpAdapter } from '../utils/noop-adapter.spec' ;
10
+ import { SseStream } from '../../router/sse-stream' ;
10
11
11
12
describe ( 'RouterResponseController' , ( ) => {
12
13
let adapter : NoopHttpAdapter ;
@@ -374,6 +375,71 @@ data: test
374
375
done ( ) ;
375
376
} ) ;
376
377
378
+ describe ( 'when writing data too densely' , ( ) => {
379
+ const DEFAULT_MAX_LISTENERS = SseStream . defaultMaxListeners ;
380
+ const MAX_LISTENERS = 1 ;
381
+ const sandbox = sinon . createSandbox ( ) ;
382
+
383
+ beforeEach ( ( ) => {
384
+ // Can't access to the internal sseStream,
385
+ // as a workround, set `defaultMaxListeners` of `SseStream` and reset the max listeners of `process`
386
+ const PROCESS_MAX_LISTENERS = process . getMaxListeners ( ) ;
387
+ SseStream . defaultMaxListeners = MAX_LISTENERS ;
388
+ process . setMaxListeners ( PROCESS_MAX_LISTENERS ) ;
389
+
390
+ const sseStream = sinon . createStubInstance ( SseStream ) ;
391
+ const originalWrite = SseStream . prototype . write ;
392
+ // Make `.write()` always return false, so as to listen `drain` event
393
+ sseStream . write . callsFake ( function ( ...args : any [ ] ) {
394
+ originalWrite . apply ( this , args ) ;
395
+ return false ;
396
+ } ) ;
397
+ sandbox . replace ( SseStream . prototype , 'write' , sseStream . write ) ;
398
+ } ) ;
399
+
400
+ afterEach ( ( ) => {
401
+ sandbox . restore ( ) ;
402
+ SseStream . defaultMaxListeners = DEFAULT_MAX_LISTENERS ;
403
+ } ) ;
404
+
405
+ it ( 'should not cause memory leak' , async ( ) => {
406
+ let maxDrainListenersExceededWarning = null ;
407
+ process . on ( 'warning' , ( warning : any ) => {
408
+ if (
409
+ warning . name === 'MaxListenersExceededWarning' &&
410
+ warning . emitter instanceof SseStream &&
411
+ warning . type === 'drain' &&
412
+ warning . count === MAX_LISTENERS + 1
413
+ ) {
414
+ maxDrainListenersExceededWarning = warning ;
415
+ }
416
+ } ) ;
417
+
418
+ const result = new Subject ( ) ;
419
+
420
+ const response = new Writable ( ) ;
421
+ response . _write = ( ) => { } ;
422
+
423
+ const request = new Writable ( ) ;
424
+ request . _write = ( ) => { } ;
425
+
426
+ routerResponseController . sse (
427
+ result ,
428
+ response as unknown as ServerResponse ,
429
+ request as unknown as IncomingMessage ,
430
+ ) ;
431
+
432
+ // Send multiple messages simultaneously
433
+ Array . from ( { length : MAX_LISTENERS + 1 } ) . forEach ( ( _ , i ) =>
434
+ result . next ( String ( i ) ) ,
435
+ ) ;
436
+
437
+ await new Promise ( resolve => process . nextTick ( resolve ) ) ;
438
+
439
+ expect ( maxDrainListenersExceededWarning ) . to . equal ( null ) ;
440
+ } ) ;
441
+ } ) ;
442
+
377
443
describe ( 'when there is an error' , ( ) => {
378
444
it ( 'should close the request' , done => {
379
445
const result = new Subject ( ) ;
0 commit comments