Remove std-fork

This commit is contained in:
Matt Bierner
2018-08-21 16:27:41 +02:00
parent 3eaa5052c2
commit ad68ff316c
3 changed files with 73 additions and 354 deletions

View File

@@ -355,6 +355,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType
}
}
private token: number = 0;
private startService(resendModels: boolean = false): Promise<ForkedTsServerProcess> {
let currentVersion = this.versionPicker.currentVersion;
@@ -372,6 +373,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType
this.requestQueue = new RequestQueue();
this.callbacks = new CallbackMap();
this.lastError = null;
let mytoken = ++this.token;
return this.servicePromise = new Promise<ForkedTsServerProcess>(async (resolve, reject) => {
try {
@@ -380,76 +382,83 @@ export default class TypeScriptServiceClient extends Disposable implements IType
const tsServerForkOptions: electron.IForkOptions = {
execArgv: debugPort ? [`--inspect=${debugPort}`] : [] // [`--debug-brk=5859`]
};
electron.fork(currentVersion.tsServerPath, tsServerForkArgs, tsServerForkOptions, this.logger, (err: any, childProcess: cp.ChildProcess | null) => {
if (err || !childProcess) {
this.lastError = err;
this.error('Starting TSServer failed with error.', err);
vscode.window.showErrorMessage(localize('serverCouldNotBeStarted', 'TypeScript language server couldn\'t be started. Error message is: {0}', err.message || err));
/* __GDPR__
"error" : {
"${include}": [
"${TypeScriptCommonProperties}"
]
}
*/
this.logTelemetry('error');
this.resetClientVersion();
const childProcess = electron.fork(currentVersion.tsServerPath, tsServerForkArgs, tsServerForkOptions, this.logger);
childProcess.once('error', (err: Error) => {
this.lastError = err;
this.error('Starting TSServer failed with error.', err);
vscode.window.showErrorMessage(localize('serverCouldNotBeStarted', 'TypeScript language server couldn\'t be started. Error message is: {0}', err.message || err));
/* __GDPR__
"error" : {
"${include}": [
"${TypeScriptCommonProperties}"
]
}
*/
this.logTelemetry('error');
this.resetClientVersion();
return;
});
this.info('Started TSServer');
const handle = new ForkedTsServerProcess(childProcess);
this.lastStart = Date.now();
handle.onError((err: Error) => {
if (this.token !== mytoken) {
// this is coming from an old process
return;
}
this.info('Started TSServer');
const handle = new ForkedTsServerProcess(childProcess);
this.lastStart = Date.now();
handle.onError((err: Error) => {
this.lastError = err;
this.error('TSServer errored with error.', err);
if (this.tsServerLogFile) {
this.error(`TSServer log file: ${this.tsServerLogFile}`);
this.lastError = err;
this.error('TSServer errored with error.', err);
if (this.tsServerLogFile) {
this.error(`TSServer log file: ${this.tsServerLogFile}`);
}
/* __GDPR__
"tsserver.error" : {
"${include}": [
"${TypeScriptCommonProperties}"
]
}
*/
this.logTelemetry('tsserver.error');
this.serviceExited(false);
});
handle.onExit((code: any) => {
if (this.token !== mytoken) {
// this is coming from an old process
return;
}
if (code === null || typeof code === 'undefined') {
this.info('TSServer exited');
} else {
this.error(`TSServer exited with code: ${code}`);
/* __GDPR__
"tsserver.error" : {
"tsserver.exitWithCode" : {
"code" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"${include}": [
"${TypeScriptCommonProperties}"
]
}
*/
this.logTelemetry('tsserver.error');
this.serviceExited(false);
});
handle.onExit((code: any) => {
if (code === null || typeof code === 'undefined') {
this.info('TSServer exited');
} else {
this.error(`TSServer exited with code: ${code}`);
/* __GDPR__
"tsserver.exitWithCode" : {
"code" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"${include}": [
"${TypeScriptCommonProperties}"
]
}
*/
this.logTelemetry('tsserver.exitWithCode', { code: code });
}
this.logTelemetry('tsserver.exitWithCode', { code: code });
}
if (this.tsServerLogFile) {
this.info(`TSServer log file: ${this.tsServerLogFile}`);
}
this.serviceExited(!this.isRestarting);
this.isRestarting = false;
});
handle.createReader(
msg => { this.dispatchMessage(msg); },
error => { this.error('ReaderError', error); });
this._onReady!.resolve();
resolve(handle);
this._onTsServerStarted.fire(currentVersion.version);
this.serviceStarted(resendModels);
if (this.tsServerLogFile) {
this.info(`TSServer log file: ${this.tsServerLogFile}`);
}
this.serviceExited(!this.isRestarting);
this.isRestarting = false;
});
handle.createReader(
msg => { this.dispatchMessage(msg); },
error => { this.error('ReaderError', error); });
this._onReady!.resolve();
resolve(handle);
this._onTsServerStarted.fire(currentVersion.version);
this.serviceStarted(resendModels);
} catch (error) {
reject(error);
}

View File

@@ -7,7 +7,6 @@ import Logger from './logger';
import * as temp from './temp';
import path = require('path');
import fs = require('fs');
import net = require('net');
import cp = require('child_process');
export interface IForkOptions {
@@ -32,32 +31,10 @@ export function getTempFile(prefix: string): string {
return path.join(getRootTempDir(), `${prefix}-${temp.makeRandomHexString(20)}.tmp`);
}
function generatePipeName(): string {
return getPipeName(temp.makeRandomHexString(40));
}
function getPipeName(name: string): string {
const fullName = 'vscode-' + name;
if (process.platform === 'win32') {
return '\\\\.\\pipe\\' + fullName + '-sock';
}
// Mac/Unix: use socket file
return path.join(getRootTempDir(), fullName + '.sock');
}
function generatePatchedEnv(
env: any,
stdInPipeName: string,
stdOutPipeName: string,
stdErrPipeName: string
): any {
function generatePatchedEnv(env: any): any {
const newEnv = Object.assign({}, env);
// Set the two unique pipe names and the electron flag as process env
newEnv['STDIN_PIPE_NAME'] = stdInPipeName;
newEnv['STDOUT_PIPE_NAME'] = stdOutPipeName;
newEnv['STDERR_PIPE_NAME'] = stdErrPipeName;
newEnv['ELECTRON_RUN_AS_NODE'] = '1';
// Ensure we always have a PATH set
@@ -69,87 +46,18 @@ export function fork(
modulePath: string,
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);
logger: Logger
): cp.ChildProcess {
const newEnv = generatePatchedEnv(process.env);
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 = <any>net.connect(stdInPipeName);
// 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
logger.info('Forking TSServer', `PATH: ${newEnv['PATH']} `);
const bootstrapperPath = require.resolve('./electronForkStart');
childProcess = cp.fork(bootstrapperPath, [modulePath].concat(args), {
return cp.fork(modulePath, args, {
silent: true,
cwd: options.cwd,
env: newEnv,
execArgv: options.execArgv
});
childProcess.once('error', (err: Error) => {
closeServer();
reject(err);
});
childProcess.once('exit', (err: Error) => {
closeServer();
reject(err);
});
}

View File

@@ -1,198 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var net = require('net'),
fs = require('fs');
var ENABLE_LOGGING = false;
var log = (function () {
if (!ENABLE_LOGGING) {
return function () { };
}
var isFirst = true;
var LOG_LOCATION = 'C:\\stdFork.log';
return function log(str: any) {
if (isFirst) {
isFirst = false;
fs.writeFileSync(LOG_LOCATION, str + '\n');
return;
}
fs.appendFileSync(LOG_LOCATION, str + '\n');
};
})();
var stdInPipeName = process.env['STDIN_PIPE_NAME'];
var stdOutPipeName = process.env['STDOUT_PIPE_NAME'];
var stdErrPipeName = process.env['STDERR_PIPE_NAME'];
log('STDIN_PIPE_NAME: ' + stdInPipeName);
log('STDOUT_PIPE_NAME: ' + stdOutPipeName);
log('STDERR_PIPE_NAME: ' + stdErrPipeName);
log('ELECTRON_RUN_AS_NODE: ' + process.env['ELECTRON_RUN_AS_NODE']);
// stdout redirection to named pipe
(function () {
log('Beginning stdout redirection...');
// Create a writing stream to the stdout pipe
var stdOutStream = net.connect(stdOutPipeName);
// unref stdOutStream to behave like a normal standard out
stdOutStream.unref();
// handle process.stdout
(<any>process).__defineGetter__('stdout', function () { return stdOutStream; });
// Create a writing stream to the stderr pipe
var stdErrStream = net.connect(stdErrPipeName);
// unref stdErrStream to behave like a normal standard out
stdErrStream.unref();
// handle process.stderr
(<any>process).__defineGetter__('stderr', function () { return stdErrStream; });
var fsWriteSyncString = function (fd: number, str: string, _position: number, encoding?: string) {
// fs.writeSync(fd, string[, position[, encoding]]);
var buf = Buffer.from(str, encoding || 'utf8');
return fsWriteSyncBuffer(fd, buf, 0, buf.length);
};
var fsWriteSyncBuffer = function (fd: number, buffer: Buffer, off: number, len: number) {
off = Math.abs(off | 0);
len = Math.abs(len | 0);
// fs.writeSync(fd, buffer, offset, length[, position]);
var 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');
}
var 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, ...)
var originalWriteSync = fs.writeSync;
fs.writeSync = function (fd: number, data: any, _position: number, _encoding?: string) {
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');
})();
// stdin redirection to named pipe
(function () {
// Begin listening to stdin pipe
var server = net.createServer(function (stream: any) {
// Stop accepting new connections, keep the existing one alive
server.close();
log('Parent process has connected to my stdin. All should be good now.');
// handle process.stdin
(<any>process).__defineGetter__('stdin', function () {
return stream;
});
// Remove myself from process.argv
process.argv.splice(1, 1);
// Load the actual program
var program = process.argv[1];
log('Loading program: ' + program);
// Unset the custom environmental variables that should not get inherited
delete process.env['STDIN_PIPE_NAME'];
delete process.env['STDOUT_PIPE_NAME'];
delete process.env['STDERR_PIPE_NAME'];
delete process.env['ELECTRON_RUN_AS_NODE'];
require(program);
log('Finished loading program.');
var stdinIsReferenced = true;
var timer = setInterval(function () {
var 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 ((<any>timer).unref) {
(<any>timer).unref();
}
});
server.listen(stdInPipeName, function () {
// signal via stdout that the parent process can now begin writing to stdin pipe
process.stdout.write('ready');
});
})();