From 69662090a3bd6b7c5a270daca0d7528b6656dabc Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 5 Jan 2022 16:39:45 -0800 Subject: [PATCH] Use node ipc for TS Server (#46418) * Use node ipc for TS Server For #46417 This lets us use Node's built-in ipc for passing messages to/from the typescript server instead of using stdio * Remove extra parse * Add extra logging when using node IPC * Split out to subclass * Extract common writeMessage method * Baseline accept --- src/server/session.ts | 4 +++ src/tsserver/nodeServer.ts | 32 +++++++++++++++++-- .../reference/api/tsserverlibrary.d.ts | 1 + 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/server/session.ts b/src/server/session.ts index 03f57a21838..b0bb42dade1 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -940,6 +940,10 @@ namespace ts.server { } return; } + this.writeMessage(msg); + } + + protected writeMessage(msg: protocol.Message) { const msgText = formatMessage(msg, this.logger, this.byteLength, this.host.newLine); perfLogger.logEvent(`Response message size: ${msgText.length}`); this.host.write(msgText); diff --git a/src/tsserver/nodeServer.ts b/src/tsserver/nodeServer.ts index e8e42042d55..c85943496e3 100644 --- a/src/tsserver/nodeServer.ts +++ b/src/tsserver/nodeServer.ts @@ -233,7 +233,7 @@ namespace ts.server { // Override sys.write because fs.writeSync is not reliable on Node 4 sys.write = (s: string) => writeMessage(sys.bufferFrom!(s, "utf8") as globalThis.Buffer); - // REVIEW: for now this implementation uses polling. + // REVIEW: for now this implementation uses polling. // The advantage of polling is that it works reliably // on all os and with network mounted files. // For 90 referenced files, the average time to detect @@ -759,12 +759,40 @@ namespace ts.server { } } + class IpcIOSession extends IOSession { + + protected writeMessage(msg: protocol.Message): void { + const verboseLogging = logger.hasLevel(LogLevel.verbose); + if (verboseLogging) { + const json = JSON.stringify(msg); + logger.info(`${msg.type}:${indent(json)}`); + } + + process.send!(msg); + } + + protected parseMessage(message: any): protocol.Request { + return message as protocol.Request; + } + + protected toStringMessage(message: any) { + return JSON.stringify(message, undefined, 2); + } + + public listen() { + process.on("message", (e: any) => { + this.onMessage(e); + }); + } + } + const eventPort: number | undefined = parseEventPort(findArgument("--eventPort")); const typingSafeListLocation = findArgument(Arguments.TypingSafeListLocation)!; // TODO: GH#18217 const typesMapLocation = findArgument(Arguments.TypesMapLocation) || combinePaths(getDirectoryPath(sys.getExecutingFilePath()), "typesMap.json"); const npmLocation = findArgument(Arguments.NpmLocation); const validateDefaultNpmLocation = hasArgument(Arguments.ValidateDefaultNpmLocation); const disableAutomaticTypingAcquisition = hasArgument("--disableAutomaticTypingAcquisition"); + const useNodeIpc = hasArgument("--useNodeIpc"); const telemetryEnabled = hasArgument(Arguments.EnableTelemetry); const commandLineTraceDir = findArgument("--traceDirectory"); const traceDir = commandLineTraceDir @@ -774,7 +802,7 @@ namespace ts.server { startTracing("server", traceDir); } - const ioSession = new IOSession(); + const ioSession = useNodeIpc ? new IpcIOSession() : new IOSession(); process.on("uncaughtException", err => { ioSession.logError(err, "unknown"); }); diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 355037ba055..f822907cd14 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -10487,6 +10487,7 @@ declare namespace ts.server { logError(err: Error, cmd: string): void; private logErrorWorker; send(msg: protocol.Message): void; + protected writeMessage(msg: protocol.Message): void; event(body: T, eventName: string): void; /** @deprecated */ output(info: any, cmdName: string, reqSeq?: number, errorMsg?: string): void;