Skip to content

Commit 7fad326

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 b7aa218 commit 7fad326

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
@@ -235,24 +235,34 @@ namespace ts.server {
235235
return `${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}.${d.getMilliseconds()}`;
236236
}
237237

238+
interface QueuedOperation {
239+
operationId: string;
240+
operation: () => void;
241+
}
242+
238243
class NodeTypingsInstaller implements ITypingsInstaller {
239244
private installer: NodeChildProcess;
240245
private installerPidReported = false;
241246
private socket: NodeSocket;
242247
private projectService: ProjectService;
243-
private throttledOperations: ThrottledOperations;
244248
private eventSender: EventSender;
249+
private activeRequestCount = 0;
250+
private requestQueue: QueuedOperation[] = [];
251+
private requestMap = createMap<QueuedOperation>(); // Maps operation ID to newest requestQueue entry with that ID
252+
253+
private static readonly maxActiveRequestCount = 10;
254+
private static readonly requestDelayMillis = 100;
255+
245256

246257
constructor(
247258
private readonly telemetryEnabled: boolean,
248259
private readonly logger: server.Logger,
249-
host: ServerHost,
260+
private readonly host: ServerHost,
250261
eventPort: number,
251262
readonly globalTypingsCacheLocation: string,
252263
readonly typingSafeListLocation: string,
253264
private readonly npmLocation: string | undefined,
254265
private newLine: string) {
255-
this.throttledOperations = new ThrottledOperations(host);
256266
if (eventPort) {
257267
const s = net.connect({ port: eventPort }, () => {
258268
this.socket = s;
@@ -333,12 +343,26 @@ namespace ts.server {
333343
this.logger.info(`Scheduling throttled operation: ${JSON.stringify(request)}`);
334344
}
335345
}
336-
this.throttledOperations.schedule(project.getProjectName(), /*ms*/ 250, () => {
346+
347+
const operationId = project.getProjectName();
348+
const operation = () => {
337349
if (this.logger.hasLevel(LogLevel.verbose)) {
338350
this.logger.info(`Sending request: ${JSON.stringify(request)}`);
339351
}
340352
this.installer.send(request);
341-
});
353+
};
354+
const queuedRequest: QueuedOperation = { operationId, operation };
355+
356+
if (this.activeRequestCount < NodeTypingsInstaller.maxActiveRequestCount) {
357+
this.scheduleRequest(queuedRequest);
358+
}
359+
else {
360+
if (this.logger.hasLevel(LogLevel.verbose)) {
361+
this.logger.info(`Deferring request for: ${operationId}`);
362+
}
363+
this.requestQueue.push(queuedRequest);
364+
this.requestMap.set(operationId, queuedRequest);
365+
}
342366
}
343367

344368
private handleMessage(response: SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes | InitializationFailedResponse) {
@@ -399,11 +423,39 @@ namespace ts.server {
399423
return;
400424
}
401425

426+
if (this.activeRequestCount > 0) {
427+
this.activeRequestCount--;
428+
}
429+
else {
430+
Debug.fail("Received too many responses");
431+
}
432+
433+
while (this.requestQueue.length > 0) {
434+
const queuedRequest = this.requestQueue.shift();
435+
if (this.requestMap.get(queuedRequest.operationId) == queuedRequest) {
436+
this.requestMap.delete(queuedRequest.operationId);
437+
this.scheduleRequest(queuedRequest);
438+
break;
439+
}
440+
441+
if (this.logger.hasLevel(LogLevel.verbose)) {
442+
this.logger.info(`Skipping defunct request for: ${queuedRequest.operationId}`);
443+
}
444+
}
445+
402446
this.projectService.updateTypingsForProject(response);
403447
if (response.kind === ActionSet && this.socket) {
404448
this.sendEvent(0, "setTypings", response);
405449
}
406450
}
451+
452+
private scheduleRequest(request: QueuedOperation) {
453+
if(this.logger.hasLevel(LogLevel.verbose)) {
454+
this.logger.info(`Scheduling request for: ${request.operationId}`);
455+
}
456+
this.activeRequestCount++;
457+
this.host.setTimeout(request.operation, NodeTypingsInstaller.requestDelayMillis);
458+
}
407459
}
408460

409461
class IOSession extends Session {

0 commit comments

Comments
 (0)