refactor(server): avoid tsserverForkStart
This commit is contained in:
parent
65e1f75be5
commit
181a337c4a
9 changed files with 199 additions and 504 deletions
|
@ -1,161 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
let net = require('net');
|
||||
let fs = require('fs');
|
||||
let ENABLE_LOGGING = false;
|
||||
let log = (function () {
|
||||
if (!ENABLE_LOGGING) {
|
||||
return function () { }; // tslint:disable-line
|
||||
}
|
||||
let isFirst = true;
|
||||
let LOG_LOCATION = 'C:\\stdFork.log';
|
||||
return function log(str) {
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
fs.writeFileSync(LOG_LOCATION, str + '\n');
|
||||
return;
|
||||
}
|
||||
fs.appendFileSync(LOG_LOCATION, str + '\n');
|
||||
};
|
||||
})();
|
||||
let stdInPipeName = process.env['STDIN_PIPE_NAME']; // tslint:disable-line
|
||||
let stdOutPipeName = process.env['STDOUT_PIPE_NAME']; // tslint:disable-line
|
||||
let stdErrPipeName = process.env['STDERR_PIPE_NAME']; // tslint:disable-line
|
||||
log('STDIN_PIPE_NAME: ' + stdInPipeName);
|
||||
log('STDOUT_PIPE_NAME: ' + stdOutPipeName);
|
||||
log('STDERR_PIPE_NAME: ' + stdErrPipeName);
|
||||
(function () {
|
||||
log('Beginning stdout redirection...');
|
||||
// Create a writing stream to the stdout pipe
|
||||
let stdOutStream = net.connect(stdOutPipeName);
|
||||
// unref stdOutStream to behave like a normal standard out
|
||||
stdOutStream.unref();
|
||||
process.__defineGetter__('stdout', function () {
|
||||
return stdOutStream;
|
||||
});
|
||||
// Create a writing stream to the stderr pipe
|
||||
let stdErrStream = net.connect(stdErrPipeName);
|
||||
// unref stdErrStream to behave like a normal standard out
|
||||
stdErrStream.unref();
|
||||
process.__defineGetter__('stderr', function () {
|
||||
return stdErrStream;
|
||||
});
|
||||
let fsWriteSyncString = function (// tslint:disable-line
|
||||
fd, str, _position, encoding) {
|
||||
// fs.writeSync(fd, string[, position[, encoding]])
|
||||
let buf = Buffer.from(str, encoding || 'utf8');
|
||||
return fsWriteSyncBuffer(fd, buf, 0, buf.length); // tslint:disable-line
|
||||
};
|
||||
let fsWriteSyncBuffer = function (// tslint:disable-line
|
||||
fd, buffer, off, len) {
|
||||
off = Math.abs(off | 0);
|
||||
len = Math.abs(len | 0);
|
||||
// fs.writeSync(fd, buffer, offset, length[, position])
|
||||
let buffer_length = buffer.length;
|
||||
if (off > buffer_length) {
|
||||
throw new Error('offset out of bounds');
|
||||
}
|
||||
if (len > buffer_length) {
|
||||
throw new Error('length out of bounds');
|
||||
}
|
||||
if (((off + len) | 0) < off) {
|
||||
throw new Error('off + len overflow');
|
||||
}
|
||||
if (buffer_length - off < len) {
|
||||
// Asking for more than is left over in the buffer
|
||||
throw new Error('off + len > buffer.length');
|
||||
}
|
||||
let slicedBuffer = buffer;
|
||||
if (off !== 0 || len !== buffer_length) {
|
||||
slicedBuffer = buffer.slice(off, off + len);
|
||||
}
|
||||
if (fd === 1) {
|
||||
stdOutStream.write(slicedBuffer);
|
||||
}
|
||||
else {
|
||||
stdErrStream.write(slicedBuffer);
|
||||
}
|
||||
return slicedBuffer.length;
|
||||
};
|
||||
// handle fs.writeSync(1, ...)
|
||||
let originalWriteSync = fs.writeSync;
|
||||
fs.writeSync = function (// tslint:disable-line
|
||||
fd, data, _position, _encoding) {
|
||||
if (fd !== 1 && fd !== 2) {
|
||||
return originalWriteSync.apply(fs, arguments);
|
||||
}
|
||||
// usage:
|
||||
// fs.writeSync(fd, buffer, offset, length[, position])
|
||||
// OR
|
||||
// fs.writeSync(fd, string[, position[, encoding]])
|
||||
if (data instanceof Buffer) {
|
||||
return fsWriteSyncBuffer.apply(null, arguments);
|
||||
}
|
||||
// For compatibility reasons with fs.writeSync, writing null will write "null", etc
|
||||
if (typeof data !== 'string') {
|
||||
data += '';
|
||||
}
|
||||
return fsWriteSyncString.apply(null, arguments);
|
||||
};
|
||||
log('Finished defining process.stdout, process.stderr and fs.writeSync');
|
||||
})();
|
||||
(function () {
|
||||
// Begin listening to stdin pipe
|
||||
let server = net.createServer(function (stream) {
|
||||
// Stop accepting new connections, keep the existing one alive
|
||||
server.close();
|
||||
log('Parent process has connected to my stdin. All should be good now.');
|
||||
process.__defineGetter__('stdin', function () {
|
||||
return stream;
|
||||
});
|
||||
// Remove myself from process.argv
|
||||
process.argv.splice(1, 1);
|
||||
// Load the actual program
|
||||
let program = process.argv[1];
|
||||
log('Loading program: ' + program);
|
||||
// Unset the custom environmental variables that should not get inherited
|
||||
delete process.env['STDIN_PIPE_NAME']; // tslint:disable-line
|
||||
delete process.env['STDOUT_PIPE_NAME']; // tslint:disable-line
|
||||
delete process.env['STDERR_PIPE_NAME']; // tslint:disable-line
|
||||
require(program);
|
||||
log('Finished loading program.');
|
||||
let stdinIsReferenced = true;
|
||||
let timer = setInterval(function () {
|
||||
let listenerCount = stream.listeners('data').length +
|
||||
stream.listeners('end').length +
|
||||
stream.listeners('close').length +
|
||||
stream.listeners('error').length;
|
||||
// log('listenerCount: ' + listenerCount)
|
||||
if (listenerCount <= 1) {
|
||||
// No more "actual" listeners, only internal node
|
||||
if (stdinIsReferenced) {
|
||||
stdinIsReferenced = false;
|
||||
// log('unreferencing stream!!!')
|
||||
stream.unref();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// There are "actual" listeners
|
||||
if (!stdinIsReferenced) {
|
||||
stdinIsReferenced = true;
|
||||
stream.ref();
|
||||
}
|
||||
}
|
||||
// log(
|
||||
// '' + stream.listeners('data').length +
|
||||
// ' ' + stream.listeners('end').length +
|
||||
// ' ' + stream.listeners('close').length +
|
||||
// ' ' + stream.listeners('error').length
|
||||
// )
|
||||
}, 1000);
|
||||
if (timer.unref) { // tslint:disable-line
|
||||
timer.unref(); // tslint:disable-line
|
||||
}
|
||||
});
|
||||
server.listen(stdInPipeName, function () {
|
||||
// signal via stdout that the parent process can now begin writing to stdin pipe
|
||||
process.stdout.write('ready');
|
||||
});
|
||||
})();
|
|
@ -29,12 +29,7 @@ export async function activate(context: ExtensionContext): Promise<API> {
|
|||
registCommand({
|
||||
id: 'tsserver.restart',
|
||||
execute: (): void => {
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
service.stop().then(() => {
|
||||
setTimeout(() => {
|
||||
service.restart()
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -345,9 +345,7 @@ export default class BufferSyncSupport {
|
|||
}
|
||||
|
||||
public listen(): void {
|
||||
if (this.listening) {
|
||||
return
|
||||
}
|
||||
if (this.listening) return
|
||||
this.listening = true
|
||||
workspace.onDidOpenTextDocument(
|
||||
this.openTextDocument,
|
||||
|
|
|
@ -58,18 +58,14 @@ export default class TsserverService implements IServiceProvider {
|
|||
this.disposables.push(this.clientHost)
|
||||
let client = this.clientHost.serviceClient
|
||||
return new Promise(resolve => {
|
||||
let started = false
|
||||
client.onTsServerStarted(() => {
|
||||
client.onReady(() => {
|
||||
Object.defineProperty(this, 'state', {
|
||||
get: () => {
|
||||
return this.clientHost.serviceClient.state
|
||||
}
|
||||
})
|
||||
this._onDidServiceReady.fire(void 0)
|
||||
if (!started) {
|
||||
started = true
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -78,10 +74,10 @@ export default class TsserverService implements IServiceProvider {
|
|||
disposeAll(this.disposables)
|
||||
}
|
||||
|
||||
public async restart(): Promise<void> {
|
||||
public restart(): void {
|
||||
if (!this.clientHost) return
|
||||
let client = this.clientHost.serviceClient
|
||||
await client.restartTsServer()
|
||||
client.restartTsServer()
|
||||
}
|
||||
|
||||
public async stop(): Promise<void> {
|
||||
|
@ -89,6 +85,5 @@ export default class TsserverService implements IServiceProvider {
|
|||
this.clientHost.reset()
|
||||
let client = this.clientHost.serviceClient
|
||||
await client.stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,13 +49,8 @@ export default class LanguageProvider {
|
|||
) {
|
||||
workspace.onDidChangeConfiguration(this.configurationChanged, this, this.disposables)
|
||||
this.configurationChanged()
|
||||
|
||||
let initialized = false
|
||||
client.onTsServerStarted(async () => { // tslint:disable-line
|
||||
if (!initialized) {
|
||||
initialized = true
|
||||
client.onReady(() => {
|
||||
this.registerProviders(client, typingsStatus)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
55
src/server/tsServerProcess.ts
Normal file
55
src/server/tsServerProcess.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import cp from 'child_process'
|
||||
import { Disposable } from 'vscode-languageserver-protocol'
|
||||
import * as Proto from './protocol'
|
||||
import { Reader } from './utils/wireProtocol'
|
||||
|
||||
export interface ToCancelOnResourceChanged {
|
||||
readonly resource: string
|
||||
cancel(): void
|
||||
}
|
||||
|
||||
export default class ForkedTsServerProcess implements Disposable {
|
||||
private readonly _reader: Reader<Proto.Response>
|
||||
|
||||
constructor(private childProcess: cp.ChildProcess) {
|
||||
this._reader = new Reader<Proto.Response>(this.childProcess.stdout)
|
||||
}
|
||||
|
||||
public readonly toCancelOnResourceChange = new Set<ToCancelOnResourceChanged>()
|
||||
|
||||
public onExit(cb: (err: any, signal: string) => void): void {
|
||||
this.childProcess.on('exit', cb)
|
||||
}
|
||||
|
||||
public write(serverRequest: Proto.Request): void {
|
||||
this.childProcess.stdin.write(
|
||||
JSON.stringify(serverRequest) + '\r\n',
|
||||
'utf8'
|
||||
)
|
||||
}
|
||||
|
||||
public onData(handler: (data: Proto.Response) => void): void {
|
||||
this._reader.onData(handler)
|
||||
}
|
||||
|
||||
public onError(handler: (err: Error) => void): void {
|
||||
this.childProcess.on('error', handler)
|
||||
this._reader.onError(handler)
|
||||
}
|
||||
|
||||
public kill(): void {
|
||||
this.toCancelOnResourceChange.clear()
|
||||
this.childProcess.kill()
|
||||
this._reader.dispose()
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.toCancelOnResourceChange.clear()
|
||||
this._reader.dispose()
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@
|
|||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import cp from 'child_process'
|
||||
import { Document, ServiceStat, Uri, window, workspace } from 'coc.nvim'
|
||||
import fs from 'fs'
|
||||
import os from 'os'
|
||||
|
@ -25,51 +24,7 @@ import Tracer from './utils/tracer'
|
|||
import { inferredProjectConfig } from './utils/tsconfig'
|
||||
import { TypeScriptVersion, TypeScriptVersionProvider } from './utils/versionProvider'
|
||||
import VersionStatus from './utils/versionStatus'
|
||||
import { Reader } from './utils/wireProtocol'
|
||||
|
||||
interface ToCancelOnResourceChanged {
|
||||
readonly resource: string
|
||||
cancel(): void
|
||||
}
|
||||
|
||||
class ForkedTsServerProcess implements Disposable {
|
||||
private readonly _reader: Reader<Proto.Response>
|
||||
|
||||
constructor(private childProcess: cp.ChildProcess) {
|
||||
this._reader = new Reader<Proto.Response>(this.childProcess.stdout)
|
||||
}
|
||||
|
||||
public readonly toCancelOnResourceChange = new Set<ToCancelOnResourceChanged>()
|
||||
|
||||
public onExit(cb: (err: any) => void): void {
|
||||
this.childProcess.on('exit', cb)
|
||||
}
|
||||
|
||||
public write(serverRequest: Proto.Request): void {
|
||||
this.childProcess.stdin.write(
|
||||
JSON.stringify(serverRequest) + '\r\n',
|
||||
'utf8'
|
||||
)
|
||||
}
|
||||
|
||||
public onData(handler: (data: Proto.Response) => void): void {
|
||||
this._reader.onData(handler)
|
||||
}
|
||||
|
||||
public onError(handler: (err: Error) => void): void {
|
||||
this.childProcess.on('error', handler)
|
||||
this._reader.onError(handler)
|
||||
}
|
||||
|
||||
public kill(): void {
|
||||
this.childProcess.kill()
|
||||
this._reader.dispose()
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._reader.dispose()
|
||||
}
|
||||
}
|
||||
import ForkedTsServerProcess, { ToCancelOnResourceChanged } from './tsServerProcess'
|
||||
|
||||
export interface TsDiagnostics {
|
||||
readonly kind: DiagnosticKind
|
||||
|
@ -78,6 +33,7 @@ export interface TsDiagnostics {
|
|||
}
|
||||
|
||||
export default class TypeScriptServiceClient implements ITypeScriptServiceClient {
|
||||
private token: number = 0
|
||||
public state = ServiceStat.Initial
|
||||
public readonly logger: Logger = new Logger()
|
||||
public readonly bufferSyncSupport: BufferSyncSupport
|
||||
|
@ -90,14 +46,13 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
|||
private versionProvider: TypeScriptVersionProvider
|
||||
private tsServerLogFile: string | null = null
|
||||
private tsServerProcess: ForkedTsServerProcess | undefined
|
||||
private servicePromise: Thenable<ForkedTsServerProcess> | null
|
||||
private lastError: Error | null
|
||||
private lastStart: number
|
||||
private numberRestarts: number
|
||||
private cancellationPipeName: string | null = null
|
||||
private _callbacks = new CallbackMap<Proto.Response>()
|
||||
private _requestQueue = new RequestQueue()
|
||||
private _pendingResponses = new Set<number>()
|
||||
private _onReady?: { promise: Promise<void>; resolve: () => void; reject: () => void }
|
||||
|
||||
private versionStatus: VersionStatus
|
||||
private readonly _onTsServerStarted = new Emitter<API>()
|
||||
|
@ -118,9 +73,15 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
|||
) {
|
||||
this.pathSeparator = path.sep
|
||||
this.lastStart = Date.now()
|
||||
this.servicePromise = null
|
||||
this.lastError = null
|
||||
this.numberRestarts = 0
|
||||
let resolve: () => void
|
||||
let reject: () => void
|
||||
const p = new Promise<void>((res, rej) => {
|
||||
resolve = res
|
||||
reject = rej
|
||||
})
|
||||
this._onReady = { promise: p, resolve: resolve!, reject: reject! }
|
||||
|
||||
this.fileConfigurationManager = new FileConfigurationManager(this)
|
||||
this._configuration = TypeScriptServiceConfiguration.loadFromWorkspace()
|
||||
this.versionProvider = new TypeScriptVersionProvider(this._configuration)
|
||||
|
@ -136,7 +97,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
|||
}, null, this.disposables)
|
||||
|
||||
this.bufferSyncSupport = new BufferSyncSupport(this, modeIds)
|
||||
this.onTsServerStarted(() => {
|
||||
this.onReady(() => {
|
||||
this.bufferSyncSupport.listen()
|
||||
})
|
||||
|
||||
|
@ -169,14 +130,12 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
|||
return this._configuration
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this.servicePromise) {
|
||||
this.servicePromise
|
||||
.then(childProcess => {
|
||||
childProcess.kill()
|
||||
})
|
||||
.then(undefined, () => void 0)
|
||||
public onReady(f: () => void): Promise<void> {
|
||||
return this._onReady!.promise.then(f)
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.tsServerProcess.kill()
|
||||
this.bufferSyncSupport.dispose()
|
||||
this.logger.dispose()
|
||||
this._onTsServerStarted.dispose()
|
||||
|
@ -192,40 +151,28 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
|||
this.logger.error(message, data)
|
||||
}
|
||||
|
||||
public restartTsServer(): Promise<any> {
|
||||
const start = () => {
|
||||
this.servicePromise = this.startService(true)
|
||||
return this.servicePromise
|
||||
}
|
||||
|
||||
if (this.servicePromise) {
|
||||
return Promise.resolve(this.servicePromise.then(childProcess => {
|
||||
public restartTsServer(): void {
|
||||
if (this.tsServerProcess) {
|
||||
this.state = ServiceStat.Stopping
|
||||
this.info('Killing TS Server')
|
||||
this.isRestarting = true
|
||||
childProcess.kill()
|
||||
this.servicePromise = null
|
||||
}).then(start))
|
||||
} else {
|
||||
return Promise.resolve(start())
|
||||
this.tsServerProcess.kill()
|
||||
}
|
||||
this.startService(true)
|
||||
}
|
||||
|
||||
public stop(): Promise<void> {
|
||||
if (!this.servicePromise) return
|
||||
return new Promise((resolve, reject) => {
|
||||
this.servicePromise.then(childProcess => {
|
||||
if (this.state == ServiceStat.Running) {
|
||||
return new Promise(resolve => {
|
||||
let { tsServerProcess } = this
|
||||
if (tsServerProcess && this.state == ServiceStat.Running) {
|
||||
this.info('Killing TS Server')
|
||||
childProcess.onExit(() => {
|
||||
tsServerProcess.onExit(() => {
|
||||
resolve()
|
||||
})
|
||||
childProcess.kill()
|
||||
this.servicePromise = null
|
||||
tsServerProcess.kill()
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
}, reject)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -259,52 +206,34 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
|||
return this._tscPath
|
||||
}
|
||||
|
||||
private service(): Thenable<ForkedTsServerProcess> {
|
||||
if (this.servicePromise) {
|
||||
return this.servicePromise
|
||||
}
|
||||
if (this.lastError) {
|
||||
return Promise.reject<ForkedTsServerProcess>(this.lastError)
|
||||
}
|
||||
return this.startService().then(() => {
|
||||
if (this.servicePromise) {
|
||||
return this.servicePromise
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public ensureServiceStarted(): void {
|
||||
if (!this.servicePromise) {
|
||||
this.startService().catch(err => {
|
||||
window.showMessage(`TSServer start failed: ${err.message}`, 'error')
|
||||
this.error(`Service start failed: ${err.stack}`)
|
||||
})
|
||||
if (!this.tsServerProcess) {
|
||||
this.startService()
|
||||
}
|
||||
}
|
||||
|
||||
private async startService(resendModels = false): Promise<ForkedTsServerProcess> {
|
||||
private startService(resendModels = false): ForkedTsServerProcess | undefined {
|
||||
const { ignoreLocalTsserver } = this.configuration
|
||||
let currentVersion: TypeScriptVersion
|
||||
if (!ignoreLocalTsserver) currentVersion = this.versionProvider.getLocalVersion()
|
||||
if (!currentVersion || !fs.existsSync(currentVersion.tsServerPath)) {
|
||||
this.info('Local tsserver not found, using bundled tsserver with coc-tsserver.')
|
||||
currentVersion = this.versionProvider.getDefaultVersion()
|
||||
}
|
||||
if (!currentVersion || !currentVersion.isValid) {
|
||||
if (this.configuration.globalTsdk) {
|
||||
window.showMessage(`Can not find typescript module, in 'tsserver.tsdk': ${this.configuration.globalTsdk}`, 'error')
|
||||
window.showErrorMessage(`Can not find typescript module, in 'tsserver.tsdk': ${this.configuration.globalTsdk}`)
|
||||
} else {
|
||||
window.showMessage(`Can not find typescript module, run ':CocInstall coc-tsserver' to fix it!`, 'error')
|
||||
window.showErrorMessage(`Can not find typescript module, run ':CocInstall coc-tsserver' to fix it!`)
|
||||
}
|
||||
return
|
||||
}
|
||||
this._apiVersion = currentVersion.version
|
||||
this._tscPath = currentVersion.tscPath
|
||||
this.versionStatus.onDidChangeTypeScriptVersion(currentVersion)
|
||||
this.lastError = null
|
||||
const tsServerForkArgs = await this.getTsServerArgs(currentVersion)
|
||||
const tsServerForkArgs = this.getTsServerArgs(currentVersion)
|
||||
const options = { execArgv: this.getExecArgv() }
|
||||
this.servicePromise = this.startProcess(currentVersion, tsServerForkArgs, options, resendModels)
|
||||
return this.servicePromise
|
||||
return this.startProcess(currentVersion, tsServerForkArgs, options, resendModels)
|
||||
}
|
||||
|
||||
private getExecArgv(): string[] {
|
||||
|
@ -322,64 +251,54 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
|||
return args
|
||||
}
|
||||
|
||||
private startProcess(currentVersion: TypeScriptVersion, args: string[], options: IForkOptions, resendModels: boolean): Promise<ForkedTsServerProcess> {
|
||||
private startProcess(currentVersion: TypeScriptVersion, args: string[], options: IForkOptions, resendModels: boolean): ForkedTsServerProcess {
|
||||
const myToken = ++this.token
|
||||
this.state = ServiceStat.Starting
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
fork(
|
||||
currentVersion.tsServerPath,
|
||||
args,
|
||||
options,
|
||||
this.logger,
|
||||
(err: any, childProcess: cp.ChildProcess | null) => {
|
||||
if (err || !childProcess) {
|
||||
this.state = ServiceStat.StartFailed
|
||||
this.lastError = err
|
||||
this.error('Starting TSServer failed with error.', err.stack)
|
||||
return
|
||||
}
|
||||
let childProcess = fork(currentVersion.tsServerPath, args, options, this.logger)
|
||||
this.state = ServiceStat.Running
|
||||
this.info('Started TSServer', JSON.stringify(currentVersion, null, 2))
|
||||
this.info('Starting TSServer', JSON.stringify(currentVersion, null, 2))
|
||||
const handle = new ForkedTsServerProcess(childProcess)
|
||||
this.tsServerProcess = handle
|
||||
this.lastStart = Date.now()
|
||||
|
||||
handle.onError((err: Error) => {
|
||||
this.lastError = err
|
||||
if (this.token != myToken) return
|
||||
window.showErrorMessage(`TypeScript language server exited with error. Error message is: ${err.message}`)
|
||||
this.error('TSServer errored with error.', err)
|
||||
this.error(`TSServer log file: ${this.tsServerLogFile || ''}`)
|
||||
window.showMessage(`TSServer errored with error. ${err.message}`, 'error')
|
||||
this.serviceExited(false)
|
||||
})
|
||||
handle.onExit((code: any) => {
|
||||
handle.onExit((code: any, signal: string) => {
|
||||
handle.dispose()
|
||||
if (this.token != myToken) return
|
||||
if (code == null) {
|
||||
this.info('TSServer normal exit')
|
||||
this.info(`TSServer exited. Signal: ${signal}`)
|
||||
} else {
|
||||
this.error(`TSServer exited with code: ${code}`)
|
||||
this.error(`TSServer exited with code: ${code}. Signal: ${signal}`)
|
||||
}
|
||||
this.info(`TSServer log file: ${this.tsServerLogFile || ''}`)
|
||||
this.serviceExited(!this.isRestarting)
|
||||
this.isRestarting = false
|
||||
handle.dispose()
|
||||
})
|
||||
handle.onData(msg => {
|
||||
this.dispatchMessage(msg)
|
||||
})
|
||||
resolve(handle)
|
||||
this.serviceStarted(resendModels)
|
||||
this._onReady!.resolve()
|
||||
this._onTsServerStarted.fire(currentVersion.version)
|
||||
return handle
|
||||
} catch (err) {
|
||||
this.state = ServiceStat.StartFailed
|
||||
this.error('Starting TSServer failed with error.', err.stack)
|
||||
return undefined
|
||||
}
|
||||
)
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public async openTsServerLogFile(): Promise<boolean> {
|
||||
const isRoot = process.getuid && process.getuid() == 0
|
||||
let echoErr = (msg: string) => {
|
||||
window.showMessage(msg, 'error')
|
||||
window.showErrorMessage(msg)
|
||||
}
|
||||
if (isRoot) {
|
||||
echoErr('Log disabled for root user.')
|
||||
|
@ -457,7 +376,6 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
|||
|
||||
private serviceExited(restart: boolean): void {
|
||||
this.state = ServiceStat.Stopped
|
||||
this.servicePromise = null
|
||||
this.tsServerLogFile = null
|
||||
this._callbacks.destroy('Service died.')
|
||||
this._callbacks = new CallbackMap<Proto.Response>()
|
||||
|
@ -479,7 +397,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
|||
}
|
||||
}
|
||||
if (startService) {
|
||||
this.startService(true) // tslint:disable-line
|
||||
this.startService(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -491,7 +409,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
|||
public toOpenedFilePath(uri: string, options: { suppressAlertOnFailure?: boolean } = {}): string | undefined {
|
||||
if (!this.bufferSyncSupport.ensureHasBuffer(uri)) {
|
||||
if (!options.suppressAlertOnFailure) {
|
||||
console.error(`Unexpected resource ${uri}`)
|
||||
this.error(`Unexpected resource ${uri}`)
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
@ -608,13 +526,14 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
|||
}
|
||||
|
||||
private fatalError(command: string, error: any): void {
|
||||
console.error(`A non-recoverable error occured while executing tsserver command: ${command}`)
|
||||
|
||||
this.error(`A non-recoverable error occured while executing tsserver command: ${command}`)
|
||||
if (this.state === ServiceStat.Running) {
|
||||
this.info('Killing TS Server by fatal error:', error)
|
||||
this.service().then(service => {
|
||||
service.kill()
|
||||
})
|
||||
let { tsServerProcess } = this
|
||||
if (tsServerProcess) {
|
||||
this.tsServerProcess = undefined
|
||||
tsServerProcess.kill()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -639,7 +558,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
|||
private executeImpl(command: string, args: any, executeInfo: { isAsync: boolean, token?: CancellationToken, expectsResult: false, lowPriority?: boolean }): undefined
|
||||
private executeImpl(command: string, args: any, executeInfo: { isAsync: boolean, token?: CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise<ServerResponse.Response<Proto.Response>>
|
||||
private executeImpl(command: string, args: any, executeInfo: { isAsync: boolean, token?: CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise<ServerResponse.Response<Proto.Response>> | undefined {
|
||||
if (this.servicePromise == null) {
|
||||
if (!this.tsServerProcess) {
|
||||
return Promise.resolve(undefined)
|
||||
}
|
||||
this.bufferSyncSupport.beforeCommand(command)
|
||||
|
@ -687,16 +606,15 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
|||
if (requestItem.expectsResponse && !requestItem.isAsync) {
|
||||
this._pendingResponses.add(requestItem.request.seq)
|
||||
}
|
||||
this.service().then(childProcess => {
|
||||
if (!this.tsServerProcess) return
|
||||
try {
|
||||
childProcess.write(serverRequest)
|
||||
this.tsServerProcess.write(serverRequest)
|
||||
} catch (err) {
|
||||
const callback = this.fetchCallback(serverRequest.seq)
|
||||
if (callback) {
|
||||
callback.onError(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private tryCancelRequest(seq: number, command: string): boolean {
|
||||
|
@ -731,7 +649,6 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
|||
if (!callback) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
this._pendingResponses.delete(seq)
|
||||
return callback
|
||||
}
|
||||
|
@ -849,7 +766,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
|||
}
|
||||
}
|
||||
|
||||
private async getTsServerArgs(currentVersion: TypeScriptVersion): Promise<string[]> {
|
||||
private getTsServerArgs(currentVersion: TypeScriptVersion): string[] {
|
||||
const args: string[] = []
|
||||
|
||||
args.push('--allowLocalPluginLoads')
|
||||
|
@ -974,11 +891,8 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
|||
|
||||
public configurePlugin(pluginName: string, configuration: {}): any {
|
||||
if (this.apiVersion.gte(API.v314)) {
|
||||
if (!this.servicePromise) return
|
||||
this.servicePromise.then(() => {
|
||||
// tslint:disable-next-line: no-floating-promises
|
||||
if (!this.tsServerProcess) return
|
||||
this.executeWithoutWaitingForResponse('configurePlugin', { pluginName, configuration })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -102,6 +102,8 @@ export default class TypeScriptServiceClientHost implements Disposable {
|
|||
)
|
||||
this.languagePerId.set(description.id, manager)
|
||||
}
|
||||
this.client.ensureServiceStarted()
|
||||
this.client.onReady(() => {
|
||||
const languageIds = new Set<string>()
|
||||
for (const plugin of pluginManager.plugins) {
|
||||
if (plugin.configNamespace && plugin.languages.length) {
|
||||
|
@ -130,11 +132,11 @@ export default class TypeScriptServiceClientHost implements Disposable {
|
|||
isExternal: true
|
||||
})
|
||||
}
|
||||
|
||||
this.client.ensureServiceStarted()
|
||||
})
|
||||
this.client.onTsServerStarted(() => {
|
||||
this.triggerAllDiagnostics()
|
||||
})
|
||||
|
||||
workspace.onDidChangeConfiguration(this.configurationChanged, this, this.disposables)
|
||||
this.configurationChanged()
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import cp from 'child_process'
|
||||
import net from 'net'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
|
@ -36,21 +35,6 @@ export function getTempDirectory(): string | undefined {
|
|||
return dir
|
||||
}
|
||||
|
||||
function generatePipeName(): string {
|
||||
return getPipeName(makeRandomHexString(40))
|
||||
}
|
||||
|
||||
function getPipeName(name: string): string | undefined {
|
||||
const fullName = 'coc-tsc-' + name
|
||||
if (process.platform === 'win32') {
|
||||
return '\\\\.\\pipe\\' + fullName + '-sock'
|
||||
}
|
||||
const tmpdir = getTempDirectory()
|
||||
if (!tmpdir) return undefined
|
||||
// Mac/Unix: use socket file
|
||||
return path.join(tmpdir, fullName + '.sock')
|
||||
}
|
||||
|
||||
export function getTempFile(name: string): string | undefined {
|
||||
const fullName = 'coc-nvim-' + name
|
||||
let dir = getTempDirectory()
|
||||
|
@ -70,20 +54,9 @@ export function createTempDirectory(name: string) {
|
|||
return res
|
||||
}
|
||||
|
||||
function generatePatchedEnv(
|
||||
env: any,
|
||||
stdInPipeName: string,
|
||||
stdOutPipeName: string,
|
||||
stdErrPipeName: string
|
||||
): any {
|
||||
function generatePatchedEnv(env: any, modulePath: string): any {
|
||||
const newEnv = Object.assign({}, env)
|
||||
|
||||
// Set the two unique pipe names and the electron flag as process env
|
||||
newEnv['STDIN_PIPE_NAME'] = stdInPipeName // tslint:disable-line
|
||||
newEnv['STDOUT_PIPE_NAME'] = stdOutPipeName // tslint:disable-line
|
||||
newEnv['STDERR_PIPE_NAME'] = stdErrPipeName // tslint:disable-line
|
||||
newEnv['TSS_LOG'] = `-level verbose -file ${path.join(os.tmpdir(), 'coc-nvim-tsc.log')}` // tslint:disable-line
|
||||
|
||||
newEnv['NODE_PATH'] = path.join(modulePath, '..', '..', '..')
|
||||
// Ensure we always have a PATH set
|
||||
newEnv['PATH'] = newEnv['PATH'] || process.env.PATH // tslint:disable-line
|
||||
return newEnv
|
||||
|
@ -94,85 +67,14 @@ export function fork(
|
|||
args: string[],
|
||||
options: IForkOptions,
|
||||
logger: Logger,
|
||||
callback: (error: any, cp: cp.ChildProcess | null) => void
|
||||
): void {
|
||||
let callbackCalled = false
|
||||
const resolve = (result: cp.ChildProcess) => {
|
||||
if (callbackCalled) return
|
||||
callbackCalled = true
|
||||
callback(null, result)
|
||||
}
|
||||
const reject = (err: any) => {
|
||||
if (callbackCalled) return
|
||||
callbackCalled = true
|
||||
callback(err, null)
|
||||
}
|
||||
|
||||
// Generate three unique pipe names
|
||||
const stdInPipeName = generatePipeName()
|
||||
const stdOutPipeName = generatePipeName()
|
||||
const stdErrPipeName = generatePipeName()
|
||||
|
||||
const newEnv = generatePatchedEnv(
|
||||
process.env,
|
||||
stdInPipeName,
|
||||
stdOutPipeName,
|
||||
stdErrPipeName
|
||||
)
|
||||
newEnv['NODE_PATH'] = path.join(modulePath, '..', '..', '..')
|
||||
|
||||
let childProcess: cp.ChildProcess
|
||||
// Begin listening to stderr pipe
|
||||
let stdErrServer = net.createServer(stdErrStream => {
|
||||
// From now on the childProcess.stderr is available for reading
|
||||
childProcess.stderr = stdErrStream
|
||||
})
|
||||
stdErrServer.listen(stdErrPipeName)
|
||||
|
||||
// Begin listening to stdout pipe
|
||||
let stdOutServer = net.createServer(stdOutStream => {
|
||||
// The child process will write exactly one chunk with content `ready` when it has installed a listener to the stdin pipe
|
||||
|
||||
stdOutStream.once('data', (_chunk: Buffer) => {
|
||||
// The child process is sending me the `ready` chunk, time to connect to the stdin pipe
|
||||
childProcess.stdin = net.connect(stdInPipeName) as any
|
||||
|
||||
// From now on the childProcess.stdout is available for reading
|
||||
childProcess.stdout = stdOutStream
|
||||
|
||||
resolve(childProcess)
|
||||
})
|
||||
})
|
||||
stdOutServer.listen(stdOutPipeName)
|
||||
|
||||
let serverClosed = false
|
||||
const closeServer = () => {
|
||||
if (serverClosed) {
|
||||
return
|
||||
}
|
||||
serverClosed = true
|
||||
stdOutServer.close()
|
||||
stdErrServer.close()
|
||||
}
|
||||
|
||||
): cp.ChildProcess {
|
||||
// Create the process
|
||||
logger.info('Forking TSServer', `PATH: ${newEnv['PATH']} `)
|
||||
|
||||
const bootstrapperPath = path.resolve(__dirname, '../bin/tsserverForkStart')
|
||||
childProcess = cp.fork(bootstrapperPath, [modulePath].concat(args), {
|
||||
logger.info('Forking TSServer', `PATH: ${modulePath} `)
|
||||
let childProcess = cp.fork(modulePath, args, {
|
||||
silent: true,
|
||||
cwd: undefined,
|
||||
env: newEnv,
|
||||
env: generatePatchedEnv(process.env, modulePath),
|
||||
execArgv: options.execArgv
|
||||
})
|
||||
|
||||
childProcess.once('error', (err: Error) => {
|
||||
closeServer()
|
||||
reject(err)
|
||||
})
|
||||
|
||||
childProcess.once('exit', (err: Error) => {
|
||||
closeServer()
|
||||
reject(err)
|
||||
})
|
||||
return childProcess
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue