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.
This commit is contained in:
parent
9c6765d5cf
commit
482e802e83
1 changed files with 57 additions and 5 deletions
|
@ -236,25 +236,35 @@ namespace ts.server {
|
||||||
return `${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}.${d.getMilliseconds()}`;
|
return `${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}.${d.getMilliseconds()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface QueuedOperation {
|
||||||
|
operationId: string;
|
||||||
|
operation: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
class NodeTypingsInstaller implements ITypingsInstaller {
|
class NodeTypingsInstaller implements ITypingsInstaller {
|
||||||
private installer: NodeChildProcess;
|
private installer: NodeChildProcess;
|
||||||
private installerPidReported = false;
|
private installerPidReported = false;
|
||||||
private socket: NodeSocket;
|
private socket: NodeSocket;
|
||||||
private projectService: ProjectService;
|
private projectService: ProjectService;
|
||||||
private throttledOperations: ThrottledOperations;
|
|
||||||
private eventSender: EventSender;
|
private eventSender: EventSender;
|
||||||
|
private activeRequestCount = 0;
|
||||||
|
private requestQueue: QueuedOperation[] = [];
|
||||||
|
private requestMap = createMap<QueuedOperation>(); // Maps operation ID to newest requestQueue entry with that ID
|
||||||
|
|
||||||
|
private static readonly maxActiveRequestCount = 10;
|
||||||
|
private static readonly requestDelayMillis = 100;
|
||||||
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly telemetryEnabled: boolean,
|
private readonly telemetryEnabled: boolean,
|
||||||
private readonly logger: server.Logger,
|
private readonly logger: server.Logger,
|
||||||
host: ServerHost,
|
private readonly host: ServerHost,
|
||||||
eventPort: number,
|
eventPort: number,
|
||||||
readonly globalTypingsCacheLocation: string,
|
readonly globalTypingsCacheLocation: string,
|
||||||
readonly typingSafeListLocation: string,
|
readonly typingSafeListLocation: string,
|
||||||
readonly typesMapLocation: string,
|
readonly typesMapLocation: string,
|
||||||
private readonly npmLocation: string | undefined,
|
private readonly npmLocation: string | undefined,
|
||||||
private newLine: string) {
|
private newLine: string) {
|
||||||
this.throttledOperations = new ThrottledOperations(host);
|
|
||||||
if (eventPort) {
|
if (eventPort) {
|
||||||
const s = net.connect({ port: eventPort }, () => {
|
const s = net.connect({ port: eventPort }, () => {
|
||||||
this.socket = s;
|
this.socket = s;
|
||||||
|
@ -338,12 +348,26 @@ namespace ts.server {
|
||||||
this.logger.info(`Scheduling throttled operation: ${JSON.stringify(request)}`);
|
this.logger.info(`Scheduling throttled operation: ${JSON.stringify(request)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.throttledOperations.schedule(project.getProjectName(), /*ms*/ 250, () => {
|
|
||||||
|
const operationId = project.getProjectName();
|
||||||
|
const operation = () => {
|
||||||
if (this.logger.hasLevel(LogLevel.verbose)) {
|
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||||
this.logger.info(`Sending request: ${JSON.stringify(request)}`);
|
this.logger.info(`Sending request: ${JSON.stringify(request)}`);
|
||||||
}
|
}
|
||||||
this.installer.send(request);
|
this.installer.send(request);
|
||||||
});
|
};
|
||||||
|
const queuedRequest: QueuedOperation = { operationId, operation };
|
||||||
|
|
||||||
|
if (this.activeRequestCount < NodeTypingsInstaller.maxActiveRequestCount) {
|
||||||
|
this.scheduleRequest(queuedRequest);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||||
|
this.logger.info(`Deferring request for: ${operationId}`);
|
||||||
|
}
|
||||||
|
this.requestQueue.push(queuedRequest);
|
||||||
|
this.requestMap.set(operationId, queuedRequest);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleMessage(response: SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes | InitializationFailedResponse) {
|
private handleMessage(response: SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes | InitializationFailedResponse) {
|
||||||
|
@ -404,11 +428,39 @@ namespace ts.server {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.activeRequestCount > 0) {
|
||||||
|
this.activeRequestCount--;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Debug.fail("Received too many responses");
|
||||||
|
}
|
||||||
|
|
||||||
|
while (this.requestQueue.length > 0) {
|
||||||
|
const queuedRequest = this.requestQueue.shift();
|
||||||
|
if (this.requestMap.get(queuedRequest.operationId) == queuedRequest) {
|
||||||
|
this.requestMap.delete(queuedRequest.operationId);
|
||||||
|
this.scheduleRequest(queuedRequest);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||||
|
this.logger.info(`Skipping defunct request for: ${queuedRequest.operationId}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.projectService.updateTypingsForProject(response);
|
this.projectService.updateTypingsForProject(response);
|
||||||
if (response.kind === ActionSet && this.socket) {
|
if (response.kind === ActionSet && this.socket) {
|
||||||
this.sendEvent(0, "setTypings", response);
|
this.sendEvent(0, "setTypings", response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private scheduleRequest(request: QueuedOperation) {
|
||||||
|
if(this.logger.hasLevel(LogLevel.verbose)) {
|
||||||
|
this.logger.info(`Scheduling request for: ${request.operationId}`);
|
||||||
|
}
|
||||||
|
this.activeRequestCount++;
|
||||||
|
this.host.setTimeout(request.operation, NodeTypingsInstaller.requestDelayMillis);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class IOSession extends Session {
|
class IOSession extends Session {
|
||||||
|
|
Loading…
Reference in a new issue