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({
|
registCommand({
|
||||||
id: 'tsserver.restart',
|
id: 'tsserver.restart',
|
||||||
execute: (): void => {
|
execute: (): void => {
|
||||||
// tslint:disable-next-line:no-floating-promises
|
|
||||||
service.stop().then(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
service.restart()
|
service.restart()
|
||||||
}, 100)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -345,9 +345,7 @@ export default class BufferSyncSupport {
|
||||||
}
|
}
|
||||||
|
|
||||||
public listen(): void {
|
public listen(): void {
|
||||||
if (this.listening) {
|
if (this.listening) return
|
||||||
return
|
|
||||||
}
|
|
||||||
this.listening = true
|
this.listening = true
|
||||||
workspace.onDidOpenTextDocument(
|
workspace.onDidOpenTextDocument(
|
||||||
this.openTextDocument,
|
this.openTextDocument,
|
||||||
|
|
|
@ -58,18 +58,14 @@ export default class TsserverService implements IServiceProvider {
|
||||||
this.disposables.push(this.clientHost)
|
this.disposables.push(this.clientHost)
|
||||||
let client = this.clientHost.serviceClient
|
let client = this.clientHost.serviceClient
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
let started = false
|
client.onReady(() => {
|
||||||
client.onTsServerStarted(() => {
|
|
||||||
Object.defineProperty(this, 'state', {
|
Object.defineProperty(this, 'state', {
|
||||||
get: () => {
|
get: () => {
|
||||||
return this.clientHost.serviceClient.state
|
return this.clientHost.serviceClient.state
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this._onDidServiceReady.fire(void 0)
|
this._onDidServiceReady.fire(void 0)
|
||||||
if (!started) {
|
|
||||||
started = true
|
|
||||||
resolve()
|
resolve()
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -78,10 +74,10 @@ export default class TsserverService implements IServiceProvider {
|
||||||
disposeAll(this.disposables)
|
disposeAll(this.disposables)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async restart(): Promise<void> {
|
public restart(): void {
|
||||||
if (!this.clientHost) return
|
if (!this.clientHost) return
|
||||||
let client = this.clientHost.serviceClient
|
let client = this.clientHost.serviceClient
|
||||||
await client.restartTsServer()
|
client.restartTsServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
public async stop(): Promise<void> {
|
public async stop(): Promise<void> {
|
||||||
|
@ -89,6 +85,5 @@ export default class TsserverService implements IServiceProvider {
|
||||||
this.clientHost.reset()
|
this.clientHost.reset()
|
||||||
let client = this.clientHost.serviceClient
|
let client = this.clientHost.serviceClient
|
||||||
await client.stop()
|
await client.stop()
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,13 +49,8 @@ export default class LanguageProvider {
|
||||||
) {
|
) {
|
||||||
workspace.onDidChangeConfiguration(this.configurationChanged, this, this.disposables)
|
workspace.onDidChangeConfiguration(this.configurationChanged, this, this.disposables)
|
||||||
this.configurationChanged()
|
this.configurationChanged()
|
||||||
|
client.onReady(() => {
|
||||||
let initialized = false
|
|
||||||
client.onTsServerStarted(async () => { // tslint:disable-line
|
|
||||||
if (!initialized) {
|
|
||||||
initialized = true
|
|
||||||
this.registerProviders(client, typingsStatus)
|
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.
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
* 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 { Document, ServiceStat, Uri, window, workspace } from 'coc.nvim'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import os from 'os'
|
import os from 'os'
|
||||||
|
@ -25,51 +24,7 @@ import Tracer from './utils/tracer'
|
||||||
import { inferredProjectConfig } from './utils/tsconfig'
|
import { inferredProjectConfig } from './utils/tsconfig'
|
||||||
import { TypeScriptVersion, TypeScriptVersionProvider } from './utils/versionProvider'
|
import { TypeScriptVersion, TypeScriptVersionProvider } from './utils/versionProvider'
|
||||||
import VersionStatus from './utils/versionStatus'
|
import VersionStatus from './utils/versionStatus'
|
||||||
import { Reader } from './utils/wireProtocol'
|
import ForkedTsServerProcess, { ToCancelOnResourceChanged } from './tsServerProcess'
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TsDiagnostics {
|
export interface TsDiagnostics {
|
||||||
readonly kind: DiagnosticKind
|
readonly kind: DiagnosticKind
|
||||||
|
@ -78,6 +33,7 @@ export interface TsDiagnostics {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class TypeScriptServiceClient implements ITypeScriptServiceClient {
|
export default class TypeScriptServiceClient implements ITypeScriptServiceClient {
|
||||||
|
private token: number = 0
|
||||||
public state = ServiceStat.Initial
|
public state = ServiceStat.Initial
|
||||||
public readonly logger: Logger = new Logger()
|
public readonly logger: Logger = new Logger()
|
||||||
public readonly bufferSyncSupport: BufferSyncSupport
|
public readonly bufferSyncSupport: BufferSyncSupport
|
||||||
|
@ -90,14 +46,13 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||||
private versionProvider: TypeScriptVersionProvider
|
private versionProvider: TypeScriptVersionProvider
|
||||||
private tsServerLogFile: string | null = null
|
private tsServerLogFile: string | null = null
|
||||||
private tsServerProcess: ForkedTsServerProcess | undefined
|
private tsServerProcess: ForkedTsServerProcess | undefined
|
||||||
private servicePromise: Thenable<ForkedTsServerProcess> | null
|
|
||||||
private lastError: Error | null
|
|
||||||
private lastStart: number
|
private lastStart: number
|
||||||
private numberRestarts: number
|
private numberRestarts: number
|
||||||
private cancellationPipeName: string | null = null
|
private cancellationPipeName: string | null = null
|
||||||
private _callbacks = new CallbackMap<Proto.Response>()
|
private _callbacks = new CallbackMap<Proto.Response>()
|
||||||
private _requestQueue = new RequestQueue()
|
private _requestQueue = new RequestQueue()
|
||||||
private _pendingResponses = new Set<number>()
|
private _pendingResponses = new Set<number>()
|
||||||
|
private _onReady?: { promise: Promise<void>; resolve: () => void; reject: () => void }
|
||||||
|
|
||||||
private versionStatus: VersionStatus
|
private versionStatus: VersionStatus
|
||||||
private readonly _onTsServerStarted = new Emitter<API>()
|
private readonly _onTsServerStarted = new Emitter<API>()
|
||||||
|
@ -118,9 +73,15 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||||
) {
|
) {
|
||||||
this.pathSeparator = path.sep
|
this.pathSeparator = path.sep
|
||||||
this.lastStart = Date.now()
|
this.lastStart = Date.now()
|
||||||
this.servicePromise = null
|
|
||||||
this.lastError = null
|
|
||||||
this.numberRestarts = 0
|
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.fileConfigurationManager = new FileConfigurationManager(this)
|
||||||
this._configuration = TypeScriptServiceConfiguration.loadFromWorkspace()
|
this._configuration = TypeScriptServiceConfiguration.loadFromWorkspace()
|
||||||
this.versionProvider = new TypeScriptVersionProvider(this._configuration)
|
this.versionProvider = new TypeScriptVersionProvider(this._configuration)
|
||||||
|
@ -136,7 +97,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||||
}, null, this.disposables)
|
}, null, this.disposables)
|
||||||
|
|
||||||
this.bufferSyncSupport = new BufferSyncSupport(this, modeIds)
|
this.bufferSyncSupport = new BufferSyncSupport(this, modeIds)
|
||||||
this.onTsServerStarted(() => {
|
this.onReady(() => {
|
||||||
this.bufferSyncSupport.listen()
|
this.bufferSyncSupport.listen()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -169,14 +130,12 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||||
return this._configuration
|
return this._configuration
|
||||||
}
|
}
|
||||||
|
|
||||||
public dispose(): void {
|
public onReady(f: () => void): Promise<void> {
|
||||||
if (this.servicePromise) {
|
return this._onReady!.promise.then(f)
|
||||||
this.servicePromise
|
|
||||||
.then(childProcess => {
|
|
||||||
childProcess.kill()
|
|
||||||
})
|
|
||||||
.then(undefined, () => void 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
this.tsServerProcess.kill()
|
||||||
this.bufferSyncSupport.dispose()
|
this.bufferSyncSupport.dispose()
|
||||||
this.logger.dispose()
|
this.logger.dispose()
|
||||||
this._onTsServerStarted.dispose()
|
this._onTsServerStarted.dispose()
|
||||||
|
@ -192,40 +151,28 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||||
this.logger.error(message, data)
|
this.logger.error(message, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
public restartTsServer(): Promise<any> {
|
public restartTsServer(): void {
|
||||||
const start = () => {
|
if (this.tsServerProcess) {
|
||||||
this.servicePromise = this.startService(true)
|
|
||||||
return this.servicePromise
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.servicePromise) {
|
|
||||||
return Promise.resolve(this.servicePromise.then(childProcess => {
|
|
||||||
this.state = ServiceStat.Stopping
|
this.state = ServiceStat.Stopping
|
||||||
this.info('Killing TS Server')
|
this.info('Killing TS Server')
|
||||||
this.isRestarting = true
|
this.isRestarting = true
|
||||||
childProcess.kill()
|
this.tsServerProcess.kill()
|
||||||
this.servicePromise = null
|
|
||||||
}).then(start))
|
|
||||||
} else {
|
|
||||||
return Promise.resolve(start())
|
|
||||||
}
|
}
|
||||||
|
this.startService(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
public stop(): Promise<void> {
|
public stop(): Promise<void> {
|
||||||
if (!this.servicePromise) return
|
return new Promise(resolve => {
|
||||||
return new Promise((resolve, reject) => {
|
let { tsServerProcess } = this
|
||||||
this.servicePromise.then(childProcess => {
|
if (tsServerProcess && this.state == ServiceStat.Running) {
|
||||||
if (this.state == ServiceStat.Running) {
|
|
||||||
this.info('Killing TS Server')
|
this.info('Killing TS Server')
|
||||||
childProcess.onExit(() => {
|
tsServerProcess.onExit(() => {
|
||||||
resolve()
|
resolve()
|
||||||
})
|
})
|
||||||
childProcess.kill()
|
tsServerProcess.kill()
|
||||||
this.servicePromise = null
|
|
||||||
} else {
|
} else {
|
||||||
resolve()
|
resolve()
|
||||||
}
|
}
|
||||||
}, reject)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,52 +206,34 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||||
return this._tscPath
|
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 {
|
public ensureServiceStarted(): void {
|
||||||
if (!this.servicePromise) {
|
if (!this.tsServerProcess) {
|
||||||
this.startService().catch(err => {
|
this.startService()
|
||||||
window.showMessage(`TSServer start failed: ${err.message}`, 'error')
|
|
||||||
this.error(`Service start failed: ${err.stack}`)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async startService(resendModels = false): Promise<ForkedTsServerProcess> {
|
private startService(resendModels = false): ForkedTsServerProcess | undefined {
|
||||||
const { ignoreLocalTsserver } = this.configuration
|
const { ignoreLocalTsserver } = this.configuration
|
||||||
let currentVersion: TypeScriptVersion
|
let currentVersion: TypeScriptVersion
|
||||||
if (!ignoreLocalTsserver) currentVersion = this.versionProvider.getLocalVersion()
|
if (!ignoreLocalTsserver) currentVersion = this.versionProvider.getLocalVersion()
|
||||||
if (!currentVersion || !fs.existsSync(currentVersion.tsServerPath)) {
|
if (!currentVersion || !fs.existsSync(currentVersion.tsServerPath)) {
|
||||||
|
this.info('Local tsserver not found, using bundled tsserver with coc-tsserver.')
|
||||||
currentVersion = this.versionProvider.getDefaultVersion()
|
currentVersion = this.versionProvider.getDefaultVersion()
|
||||||
}
|
}
|
||||||
if (!currentVersion || !currentVersion.isValid) {
|
if (!currentVersion || !currentVersion.isValid) {
|
||||||
if (this.configuration.globalTsdk) {
|
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 {
|
} 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
|
return
|
||||||
}
|
}
|
||||||
this._apiVersion = currentVersion.version
|
this._apiVersion = currentVersion.version
|
||||||
this._tscPath = currentVersion.tscPath
|
this._tscPath = currentVersion.tscPath
|
||||||
this.versionStatus.onDidChangeTypeScriptVersion(currentVersion)
|
this.versionStatus.onDidChangeTypeScriptVersion(currentVersion)
|
||||||
this.lastError = null
|
const tsServerForkArgs = this.getTsServerArgs(currentVersion)
|
||||||
const tsServerForkArgs = await this.getTsServerArgs(currentVersion)
|
|
||||||
const options = { execArgv: this.getExecArgv() }
|
const options = { execArgv: this.getExecArgv() }
|
||||||
this.servicePromise = this.startProcess(currentVersion, tsServerForkArgs, options, resendModels)
|
return this.startProcess(currentVersion, tsServerForkArgs, options, resendModels)
|
||||||
return this.servicePromise
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getExecArgv(): string[] {
|
private getExecArgv(): string[] {
|
||||||
|
@ -322,64 +251,54 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||||
return args
|
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
|
this.state = ServiceStat.Starting
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
try {
|
try {
|
||||||
fork(
|
let childProcess = fork(currentVersion.tsServerPath, args, options, this.logger)
|
||||||
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
|
|
||||||
}
|
|
||||||
this.state = ServiceStat.Running
|
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)
|
const handle = new ForkedTsServerProcess(childProcess)
|
||||||
this.tsServerProcess = handle
|
this.tsServerProcess = handle
|
||||||
this.lastStart = Date.now()
|
this.lastStart = Date.now()
|
||||||
|
|
||||||
handle.onError((err: Error) => {
|
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 errored with error.', err)
|
||||||
this.error(`TSServer log file: ${this.tsServerLogFile || ''}`)
|
this.error(`TSServer log file: ${this.tsServerLogFile || ''}`)
|
||||||
window.showMessage(`TSServer errored with error. ${err.message}`, 'error')
|
window.showMessage(`TSServer errored with error. ${err.message}`, 'error')
|
||||||
this.serviceExited(false)
|
this.serviceExited(false)
|
||||||
})
|
})
|
||||||
handle.onExit((code: any) => {
|
handle.onExit((code: any, signal: string) => {
|
||||||
|
handle.dispose()
|
||||||
|
if (this.token != myToken) return
|
||||||
if (code == null) {
|
if (code == null) {
|
||||||
this.info('TSServer normal exit')
|
this.info(`TSServer exited. Signal: ${signal}`)
|
||||||
} else {
|
} 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.info(`TSServer log file: ${this.tsServerLogFile || ''}`)
|
||||||
this.serviceExited(!this.isRestarting)
|
this.serviceExited(!this.isRestarting)
|
||||||
this.isRestarting = false
|
this.isRestarting = false
|
||||||
handle.dispose()
|
|
||||||
})
|
})
|
||||||
handle.onData(msg => {
|
handle.onData(msg => {
|
||||||
this.dispatchMessage(msg)
|
this.dispatchMessage(msg)
|
||||||
})
|
})
|
||||||
resolve(handle)
|
|
||||||
this.serviceStarted(resendModels)
|
this.serviceStarted(resendModels)
|
||||||
|
this._onReady!.resolve()
|
||||||
this._onTsServerStarted.fire(currentVersion.version)
|
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> {
|
public async openTsServerLogFile(): Promise<boolean> {
|
||||||
const isRoot = process.getuid && process.getuid() == 0
|
const isRoot = process.getuid && process.getuid() == 0
|
||||||
let echoErr = (msg: string) => {
|
let echoErr = (msg: string) => {
|
||||||
window.showMessage(msg, 'error')
|
window.showErrorMessage(msg)
|
||||||
}
|
}
|
||||||
if (isRoot) {
|
if (isRoot) {
|
||||||
echoErr('Log disabled for root user.')
|
echoErr('Log disabled for root user.')
|
||||||
|
@ -457,7 +376,6 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||||
|
|
||||||
private serviceExited(restart: boolean): void {
|
private serviceExited(restart: boolean): void {
|
||||||
this.state = ServiceStat.Stopped
|
this.state = ServiceStat.Stopped
|
||||||
this.servicePromise = null
|
|
||||||
this.tsServerLogFile = null
|
this.tsServerLogFile = null
|
||||||
this._callbacks.destroy('Service died.')
|
this._callbacks.destroy('Service died.')
|
||||||
this._callbacks = new CallbackMap<Proto.Response>()
|
this._callbacks = new CallbackMap<Proto.Response>()
|
||||||
|
@ -479,7 +397,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (startService) {
|
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 {
|
public toOpenedFilePath(uri: string, options: { suppressAlertOnFailure?: boolean } = {}): string | undefined {
|
||||||
if (!this.bufferSyncSupport.ensureHasBuffer(uri)) {
|
if (!this.bufferSyncSupport.ensureHasBuffer(uri)) {
|
||||||
if (!options.suppressAlertOnFailure) {
|
if (!options.suppressAlertOnFailure) {
|
||||||
console.error(`Unexpected resource ${uri}`)
|
this.error(`Unexpected resource ${uri}`)
|
||||||
}
|
}
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
@ -608,13 +526,14 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||||
}
|
}
|
||||||
|
|
||||||
private fatalError(command: string, error: any): void {
|
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) {
|
if (this.state === ServiceStat.Running) {
|
||||||
this.info('Killing TS Server by fatal error:', error)
|
this.info('Killing TS Server by fatal error:', error)
|
||||||
this.service().then(service => {
|
let { tsServerProcess } = this
|
||||||
service.kill()
|
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: 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>>
|
||||||
private executeImpl(command: string, args: any, executeInfo: { isAsync: boolean, token?: CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise<ServerResponse.Response<Proto.Response>> | undefined {
|
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)
|
return Promise.resolve(undefined)
|
||||||
}
|
}
|
||||||
this.bufferSyncSupport.beforeCommand(command)
|
this.bufferSyncSupport.beforeCommand(command)
|
||||||
|
@ -687,16 +606,15 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||||
if (requestItem.expectsResponse && !requestItem.isAsync) {
|
if (requestItem.expectsResponse && !requestItem.isAsync) {
|
||||||
this._pendingResponses.add(requestItem.request.seq)
|
this._pendingResponses.add(requestItem.request.seq)
|
||||||
}
|
}
|
||||||
this.service().then(childProcess => {
|
if (!this.tsServerProcess) return
|
||||||
try {
|
try {
|
||||||
childProcess.write(serverRequest)
|
this.tsServerProcess.write(serverRequest)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const callback = this.fetchCallback(serverRequest.seq)
|
const callback = this.fetchCallback(serverRequest.seq)
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback.onError(err)
|
callback.onError(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private tryCancelRequest(seq: number, command: string): boolean {
|
private tryCancelRequest(seq: number, command: string): boolean {
|
||||||
|
@ -731,7 +649,6 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||||
if (!callback) {
|
if (!callback) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
this._pendingResponses.delete(seq)
|
this._pendingResponses.delete(seq)
|
||||||
return callback
|
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[] = []
|
const args: string[] = []
|
||||||
|
|
||||||
args.push('--allowLocalPluginLoads')
|
args.push('--allowLocalPluginLoads')
|
||||||
|
@ -974,11 +891,8 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||||
|
|
||||||
public configurePlugin(pluginName: string, configuration: {}): any {
|
public configurePlugin(pluginName: string, configuration: {}): any {
|
||||||
if (this.apiVersion.gte(API.v314)) {
|
if (this.apiVersion.gte(API.v314)) {
|
||||||
if (!this.servicePromise) return
|
if (!this.tsServerProcess) return
|
||||||
this.servicePromise.then(() => {
|
|
||||||
// tslint:disable-next-line: no-floating-promises
|
|
||||||
this.executeWithoutWaitingForResponse('configurePlugin', { pluginName, configuration })
|
this.executeWithoutWaitingForResponse('configurePlugin', { pluginName, configuration })
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -102,6 +102,8 @@ export default class TypeScriptServiceClientHost implements Disposable {
|
||||||
)
|
)
|
||||||
this.languagePerId.set(description.id, manager)
|
this.languagePerId.set(description.id, manager)
|
||||||
}
|
}
|
||||||
|
this.client.ensureServiceStarted()
|
||||||
|
this.client.onReady(() => {
|
||||||
const languageIds = new Set<string>()
|
const languageIds = new Set<string>()
|
||||||
for (const plugin of pluginManager.plugins) {
|
for (const plugin of pluginManager.plugins) {
|
||||||
if (plugin.configNamespace && plugin.languages.length) {
|
if (plugin.configNamespace && plugin.languages.length) {
|
||||||
|
@ -130,11 +132,11 @@ export default class TypeScriptServiceClientHost implements Disposable {
|
||||||
isExternal: true
|
isExternal: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
})
|
||||||
this.client.ensureServiceStarted()
|
|
||||||
this.client.onTsServerStarted(() => {
|
this.client.onTsServerStarted(() => {
|
||||||
this.triggerAllDiagnostics()
|
this.triggerAllDiagnostics()
|
||||||
})
|
})
|
||||||
|
|
||||||
workspace.onDidChangeConfiguration(this.configurationChanged, this, this.disposables)
|
workspace.onDidChangeConfiguration(this.configurationChanged, this, this.disposables)
|
||||||
this.configurationChanged()
|
this.configurationChanged()
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
import cp from 'child_process'
|
import cp from 'child_process'
|
||||||
import net from 'net'
|
|
||||||
import os from 'os'
|
import os from 'os'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
|
@ -36,21 +35,6 @@ export function getTempDirectory(): string | undefined {
|
||||||
return dir
|
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 {
|
export function getTempFile(name: string): string | undefined {
|
||||||
const fullName = 'coc-nvim-' + name
|
const fullName = 'coc-nvim-' + name
|
||||||
let dir = getTempDirectory()
|
let dir = getTempDirectory()
|
||||||
|
@ -70,20 +54,9 @@ export function createTempDirectory(name: string) {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
function generatePatchedEnv(
|
function generatePatchedEnv(env: any, modulePath: string): any {
|
||||||
env: any,
|
|
||||||
stdInPipeName: string,
|
|
||||||
stdOutPipeName: string,
|
|
||||||
stdErrPipeName: string
|
|
||||||
): any {
|
|
||||||
const newEnv = Object.assign({}, env)
|
const newEnv = Object.assign({}, env)
|
||||||
|
newEnv['NODE_PATH'] = path.join(modulePath, '..', '..', '..')
|
||||||
// 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
|
|
||||||
|
|
||||||
// Ensure we always have a PATH set
|
// Ensure we always have a PATH set
|
||||||
newEnv['PATH'] = newEnv['PATH'] || process.env.PATH // tslint:disable-line
|
newEnv['PATH'] = newEnv['PATH'] || process.env.PATH // tslint:disable-line
|
||||||
return newEnv
|
return newEnv
|
||||||
|
@ -94,85 +67,14 @@ export function fork(
|
||||||
args: string[],
|
args: string[],
|
||||||
options: IForkOptions,
|
options: IForkOptions,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
callback: (error: any, cp: cp.ChildProcess | null) => void
|
): cp.ChildProcess {
|
||||||
): 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()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the process
|
// Create the process
|
||||||
logger.info('Forking TSServer', `PATH: ${newEnv['PATH']} `)
|
logger.info('Forking TSServer', `PATH: ${modulePath} `)
|
||||||
|
let childProcess = cp.fork(modulePath, args, {
|
||||||
const bootstrapperPath = path.resolve(__dirname, '../bin/tsserverForkStart')
|
|
||||||
childProcess = cp.fork(bootstrapperPath, [modulePath].concat(args), {
|
|
||||||
silent: true,
|
silent: true,
|
||||||
cwd: undefined,
|
cwd: undefined,
|
||||||
env: newEnv,
|
env: generatePatchedEnv(process.env, modulePath),
|
||||||
execArgv: options.execArgv
|
execArgv: options.execArgv
|
||||||
})
|
})
|
||||||
|
return childProcess
|
||||||
childProcess.once('error', (err: Error) => {
|
|
||||||
closeServer()
|
|
||||||
reject(err)
|
|
||||||
})
|
|
||||||
|
|
||||||
childProcess.once('exit', (err: Error) => {
|
|
||||||
closeServer()
|
|
||||||
reject(err)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue