@@ -236,25 +236,40 @@ namespace ts.server {
236
236
return `${ d . getHours ( ) } :${ d . getMinutes ( ) } :${ d . getSeconds ( ) } .${ d . getMilliseconds ( ) } ` ;
237
237
}
238
238
239
+ interface QueuedOperation {
240
+ operationId : string ;
241
+ operation : ( ) => void ;
242
+ }
243
+
239
244
class NodeTypingsInstaller implements ITypingsInstaller {
240
245
private installer : NodeChildProcess ;
241
246
private installerPidReported = false ;
242
247
private socket : NodeSocket ;
243
248
private projectService : ProjectService ;
244
- private throttledOperations : ThrottledOperations ;
245
249
private eventSender : EventSender ;
250
+ private activeRequestCount = 0 ;
251
+ private requestQueue : QueuedOperation [ ] = [ ] ;
252
+ private requestMap = createMap < QueuedOperation > ( ) ; // Maps operation ID to newest requestQueue entry with that ID
253
+
254
+ // This number is essentially arbitrary. Processing more than one typings request
255
+ // at a time makes sense, but having too many in the pipe results in a hang
256
+ // (see https://github.com/nodejs/node/issues/7657).
257
+ // It would be preferable to base our limit on the amount of space left in the
258
+ // buffer, but we have yet to find a way to retrieve that value.
259
+ private static readonly maxActiveRequestCount = 10 ;
260
+ private static readonly requestDelayMillis = 100 ;
261
+
246
262
247
263
constructor (
248
264
private readonly telemetryEnabled : boolean ,
249
265
private readonly logger : server . Logger ,
250
- host : ServerHost ,
266
+ private readonly host : ServerHost ,
251
267
eventPort : number ,
252
268
readonly globalTypingsCacheLocation : string ,
253
269
readonly typingSafeListLocation : string ,
254
270
readonly typesMapLocation : string ,
255
271
private readonly npmLocation : string | undefined ,
256
272
private newLine : string ) {
257
- this . throttledOperations = new ThrottledOperations ( host ) ;
258
273
if ( eventPort ) {
259
274
const s = net . connect ( { port : eventPort } , ( ) => {
260
275
this . socket = s ;
@@ -338,12 +353,26 @@ namespace ts.server {
338
353
this . logger . info ( `Scheduling throttled operation: ${ JSON . stringify ( request ) } ` ) ;
339
354
}
340
355
}
341
- this . throttledOperations . schedule ( project . getProjectName ( ) , /*ms*/ 250 , ( ) => {
356
+
357
+ const operationId = project . getProjectName ( ) ;
358
+ const operation = ( ) => {
342
359
if ( this . logger . hasLevel ( LogLevel . verbose ) ) {
343
360
this . logger . info ( `Sending request: ${ JSON . stringify ( request ) } ` ) ;
344
361
}
345
362
this . installer . send ( request ) ;
346
- } ) ;
363
+ } ;
364
+ const queuedRequest : QueuedOperation = { operationId, operation } ;
365
+
366
+ if ( this . activeRequestCount < NodeTypingsInstaller . maxActiveRequestCount ) {
367
+ this . scheduleRequest ( queuedRequest ) ;
368
+ }
369
+ else {
370
+ if ( this . logger . hasLevel ( LogLevel . verbose ) ) {
371
+ this . logger . info ( `Deferring request for: ${ operationId } ` ) ;
372
+ }
373
+ this . requestQueue . push ( queuedRequest ) ;
374
+ this . requestMap . set ( operationId , queuedRequest ) ;
375
+ }
347
376
}
348
377
349
378
private handleMessage ( response : SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes | InitializationFailedResponse ) {
@@ -404,11 +433,39 @@ namespace ts.server {
404
433
return ;
405
434
}
406
435
436
+ if ( this . activeRequestCount > 0 ) {
437
+ this . activeRequestCount -- ;
438
+ }
439
+ else {
440
+ Debug . fail ( "Received too many responses" ) ;
441
+ }
442
+
443
+ while ( this . requestQueue . length > 0 ) {
444
+ const queuedRequest = this . requestQueue . shift ( ) ;
445
+ if ( this . requestMap . get ( queuedRequest . operationId ) === queuedRequest ) {
446
+ this . requestMap . delete ( queuedRequest . operationId ) ;
447
+ this . scheduleRequest ( queuedRequest ) ;
448
+ break ;
449
+ }
450
+
451
+ if ( this . logger . hasLevel ( LogLevel . verbose ) ) {
452
+ this . logger . info ( `Skipping defunct request for: ${ queuedRequest . operationId } ` ) ;
453
+ }
454
+ }
455
+
407
456
this . projectService . updateTypingsForProject ( response ) ;
408
457
if ( response . kind === ActionSet && this . socket ) {
409
458
this . sendEvent ( 0 , "setTypings" , response ) ;
410
459
}
411
460
}
461
+
462
+ private scheduleRequest ( request : QueuedOperation ) {
463
+ if ( this . logger . hasLevel ( LogLevel . verbose ) ) {
464
+ this . logger . info ( `Scheduling request for: ${ request . operationId } ` ) ;
465
+ }
466
+ this . activeRequestCount ++ ;
467
+ this . host . setTimeout ( request . operation , NodeTypingsInstaller . requestDelayMillis ) ;
468
+ }
412
469
}
413
470
414
471
class IOSession extends Session {
0 commit comments