Skip to content

Commit 482e802

Browse files
committed
Limit the number of unanswered typings installer requests
If we send them all at once, we (apparently) hit a buffer limit in the node IPC channel and both TS Server and the typings installer become unresponsive.
1 parent 9c6765d commit 482e802

File tree

1 file changed

+57
-5
lines changed

1 file changed

+57
-5
lines changed

src/server/server.ts

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -236,25 +236,35 @@ namespace ts.server {
236236
return `${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}.${d.getMilliseconds()}`;
237237
}
238238

239+
interface QueuedOperation {
240+
operationId: string;
241+
operation: () => void;
242+
}
243+
239244
class NodeTypingsInstaller implements ITypingsInstaller {
240245
private installer: NodeChildProcess;
241246
private installerPidReported = false;
242247
private socket: NodeSocket;
243248
private projectService: ProjectService;
244-
private throttledOperations: ThrottledOperations;
245249
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+
private static readonly maxActiveRequestCount = 10;
255+
private static readonly requestDelayMillis = 100;
256+
246257

247258
constructor(
248259
private readonly telemetryEnabled: boolean,
249260
private readonly logger: server.Logger,
250-
host: ServerHost,
261+
private readonly host: ServerHost,
251262
eventPort: number,
252263
readonly globalTypingsCacheLocation: string,
253264
readonly typingSafeListLocation: string,
254265
readonly typesMapLocation: string,
255266
private readonly npmLocation: string | undefined,
256267
private newLine: string) {
257-
this.throttledOperations = new ThrottledOperations(host);
258268
if (eventPort) {
259269
const s = net.connect({ port: eventPort }, () => {
260270
this.socket = s;
@@ -338,12 +348,26 @@ namespace ts.server {
338348
this.logger.info(`Scheduling throttled operation: ${JSON.stringify(request)}`);
339349
}
340350
}
341-
this.throttledOperations.schedule(project.getProjectName(), /*ms*/ 250, () => {
351+
352+
const operationId = project.getProjectName();
353+
const operation = () => {
342354
if (this.logger.hasLevel(LogLevel.verbose)) {
343355
this.logger.info(`Sending request: ${JSON.stringify(request)}`);
344356
}
345357
this.installer.send(request);
346-
});
358+
};
359+
const queuedRequest: QueuedOperation = { operationId, operation };
360+
361+
if (this.activeRequestCount < NodeTypingsInstaller.maxActiveRequestCount) {
362+
this.scheduleRequest(queuedRequest);
363+
}
364+
else {
365+
if (this.logger.hasLevel(LogLevel.verbose)) {
366+
this.logger.info(`Deferring request for: ${operationId}`);
367+
}
368+
this.requestQueue.push(queuedRequest);
369+
this.requestMap.set(operationId, queuedRequest);
370+
}
347371
}
348372

349373
private handleMessage(response: SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes | InitializationFailedResponse) {
@@ -404,11 +428,39 @@ namespace ts.server {
404428
return;
405429
}
406430

431+
if (this.activeRequestCount > 0) {
432+
this.activeRequestCount--;
433+
}
434+
else {
435+
Debug.fail("Received too many responses");
436+
}
437+
438+
while (this.requestQueue.length > 0) {
439+
const queuedRequest = this.requestQueue.shift();
440+
if (this.requestMap.get(queuedRequest.operationId) == queuedRequest) {
441+
this.requestMap.delete(queuedRequest.operationId);
442+
this.scheduleRequest(queuedRequest);
443+
break;
444+
}
445+
446+
if (this.logger.hasLevel(LogLevel.verbose)) {
447+
this.logger.info(`Skipping defunct request for: ${queuedRequest.operationId}`);
448+
}
449+
}
450+
407451
this.projectService.updateTypingsForProject(response);
408452
if (response.kind === ActionSet && this.socket) {
409453
this.sendEvent(0, "setTypings", response);
410454
}
411455
}
456+
457+
private scheduleRequest(request: QueuedOperation) {
458+
if(this.logger.hasLevel(LogLevel.verbose)) {
459+
this.logger.info(`Scheduling request for: ${request.operationId}`);
460+
}
461+
this.activeRequestCount++;
462+
this.host.setTimeout(request.operation, NodeTypingsInstaller.requestDelayMillis);
463+
}
412464
}
413465

414466
class IOSession extends Session {

0 commit comments

Comments
 (0)