mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-23 19:59:37 +00:00
Remove std-fork
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
})();
|
||||
Reference in New Issue
Block a user