mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-28 04:23:32 +01:00
Clean up thred service
This commit is contained in:
@@ -132,7 +132,13 @@ class ExtensionHostProcessManager {
|
||||
PIPE_LOGGING: 'true',
|
||||
VERBOSE_LOGGING: true,
|
||||
VSCODE_WINDOW_ID: String(this.windowService.getWindowId())
|
||||
})
|
||||
}),
|
||||
// We only detach the extension host on windows. Linux and Mac orphan by default
|
||||
// and detach under Linux and Mac create another process group.
|
||||
// We detach because we have noticed that when the renderer exits, its child processes
|
||||
// (i.e. extension host) is taken down in a brutal fashion by the OS
|
||||
detached: !!isWindows,
|
||||
onExtensionHostMessage
|
||||
};
|
||||
|
||||
// Help in case we fail to start it
|
||||
@@ -145,20 +151,16 @@ class ExtensionHostProcessManager {
|
||||
}
|
||||
|
||||
// Initialize extension host process with hand shakes
|
||||
this.initializeExtensionHostProcess = new TPromise<ChildProcess>((c, e) => {
|
||||
this.initializeExtensionHostProcess = this.doInitializeExtensionHostProcess(opts);
|
||||
}
|
||||
|
||||
private doInitializeExtensionHostProcess(opts: any): TPromise<ChildProcess> {
|
||||
return new TPromise<ChildProcess>((c, e) => {
|
||||
// Resolve additional execution args (e.g. debug)
|
||||
return this.resolveDebugPort(this.environmentService.debugExtensionHost.port, port => {
|
||||
this.resolveDebugPort(this.environmentService.debugExtensionHost.port).then(port => {
|
||||
if (port) {
|
||||
opts.execArgv = ['--nolazy', (this.isExtensionDevelopmentDebugging ? '--debug-brk=' : '--debug=') + port];
|
||||
}
|
||||
// We only detach the extension host on windows. Linux and Mac orphan by default
|
||||
// and detach under Linux and Mac create another process group.
|
||||
if (isWindows) {
|
||||
// We detach because we have noticed that when the renderer exits, its child processes
|
||||
// (i.e. extension host) is taken down in a brutal fashion by the OS
|
||||
opts.detached = true;
|
||||
}
|
||||
|
||||
// Run Extension Host as fork of current process
|
||||
this.extensionHostProcessHandle = fork(URI.parse(require.toUrl('bootstrap')).fsPath, ['--type=extensionHost'], opts);
|
||||
@@ -172,159 +174,165 @@ class ExtensionHostProcessManager {
|
||||
}
|
||||
|
||||
// Messages from Extension host
|
||||
this.extensionHostProcessHandle.on('message', (msg) => {
|
||||
|
||||
// 1) Host is ready to receive messages, initialize it
|
||||
if (msg === 'ready') {
|
||||
if (this.initializeTimer) {
|
||||
window.clearTimeout(this.initializeTimer);
|
||||
}
|
||||
|
||||
let initPayload = stringify({
|
||||
parentPid: process.pid,
|
||||
environment: {
|
||||
appSettingsHome: this.environmentService.appSettingsHome,
|
||||
disableExtensions: this.environmentService.disableExtensions,
|
||||
userExtensionsHome: this.environmentService.extensionsPath,
|
||||
extensionDevelopmentPath: this.environmentService.extensionDevelopmentPath,
|
||||
extensionTestsPath: this.environmentService.extensionTestsPath
|
||||
},
|
||||
contextService: {
|
||||
workspace: this.contextService.getWorkspace()
|
||||
}
|
||||
});
|
||||
|
||||
this.extensionHostProcessHandle.send(initPayload);
|
||||
}
|
||||
|
||||
// 2) Host is initialized
|
||||
else if (msg === 'initialized') {
|
||||
this.unsentMessages.forEach(m => this.postMessage(m));
|
||||
this.unsentMessages = [];
|
||||
|
||||
this.extensionHostProcessReady = true;
|
||||
this.extensionHostProcessHandle.on('message', msg => {
|
||||
if (this.onMessaage(msg, opts.onExtensionHostMessage)) {
|
||||
c(this.extensionHostProcessHandle);
|
||||
}
|
||||
|
||||
// Support logging from extension host
|
||||
else if (msg && (<ILogEntry>msg).type === '__$console') {
|
||||
let logEntry: ILogEntry = msg;
|
||||
|
||||
let args = [];
|
||||
try {
|
||||
let parsed = JSON.parse(logEntry.arguments);
|
||||
args.push(...Object.getOwnPropertyNames(parsed).map(o => parsed[o]));
|
||||
} catch (error) {
|
||||
args.push(logEntry.arguments);
|
||||
}
|
||||
|
||||
// If the first argument is a string, check for % which indicates that the message
|
||||
// uses substitution for variables. In this case, we cannot just inject our colored
|
||||
// [Extension Host] to the front because it breaks substitution.
|
||||
let consoleArgs = [];
|
||||
if (typeof args[0] === 'string' && args[0].indexOf('%') >= 0) {
|
||||
consoleArgs = [`%c[Extension Host]%c ${args[0]}`, 'color: blue', 'color: black', ...args.slice(1)];
|
||||
} else {
|
||||
consoleArgs = ['%c[Extension Host]', 'color: blue', ...args];
|
||||
}
|
||||
|
||||
// Send to local console unless we run tests from cli
|
||||
if (!this.isExtensionDevelopmentTestFromCli) {
|
||||
console[logEntry.severity].apply(console, consoleArgs);
|
||||
}
|
||||
|
||||
// Log on main side if running tests from cli
|
||||
if (this.isExtensionDevelopmentTestFromCli) {
|
||||
ipc.send('vscode:log', logEntry);
|
||||
}
|
||||
|
||||
// Broadcast to other windows if we are in development mode
|
||||
else if (!this.environmentService.isBuilt || this.isExtensionDevelopmentHost) {
|
||||
this.windowService.broadcast({
|
||||
channel: EXTENSION_LOG_BROADCAST_CHANNEL,
|
||||
payload: logEntry
|
||||
}, this.environmentService.extensionDevelopmentPath /* target */);
|
||||
}
|
||||
}
|
||||
|
||||
// Any other message goes to the callback
|
||||
else {
|
||||
onExtensionHostMessage(msg);
|
||||
}
|
||||
});
|
||||
|
||||
// Lifecycle
|
||||
let onExit = () => this.terminate();
|
||||
process.once('exit', onExit);
|
||||
|
||||
this.extensionHostProcessHandle.on('error', (err) => {
|
||||
let errorMessage = toErrorMessage(err);
|
||||
if (errorMessage === this.lastExtensionHostError) {
|
||||
return; // prevent error spam
|
||||
}
|
||||
|
||||
this.lastExtensionHostError = errorMessage;
|
||||
|
||||
this.messageService.show(Severity.Error, nls.localize('extensionHostProcess.error', "Error from the extension host: {0}", errorMessage));
|
||||
});
|
||||
|
||||
this.extensionHostProcessHandle.on('exit', (code: any, signal: any) => {
|
||||
process.removeListener('exit', onExit);
|
||||
|
||||
if (!this.terminating) {
|
||||
|
||||
// Unexpected termination
|
||||
if (!this.isExtensionDevelopmentHost) {
|
||||
this.messageService.show(Severity.Error, {
|
||||
message: nls.localize('extensionHostProcess.crash', "Extension host terminated unexpectedly. Please reload the window to recover."),
|
||||
actions: [this.instantiationService.createInstance(ReloadWindowAction, ReloadWindowAction.ID, ReloadWindowAction.LABEL)]
|
||||
});
|
||||
console.error('Extension host terminated unexpectedly. Code: ', code, ' Signal: ', signal);
|
||||
}
|
||||
|
||||
// Expected development extension termination: When the extension host goes down we also shutdown the window
|
||||
else if (!this.isExtensionDevelopmentTestFromCli) {
|
||||
this.windowService.getWindow().close();
|
||||
}
|
||||
|
||||
// When CLI testing make sure to exit with proper exit code
|
||||
else {
|
||||
ipc.send('vscode:exit', code);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.extensionHostProcessHandle.on('error', (err) => this.onError(err));
|
||||
this.extensionHostProcessHandle.on('exit', (code: any, signal: any) => this.onExit(code, signal, onExit));
|
||||
});
|
||||
}, () => this.terminate());
|
||||
}
|
||||
|
||||
private resolveDebugPort(extensionHostPort: number, clb: (port: number) => void): void {
|
||||
|
||||
// Check for a free debugging port
|
||||
if (typeof extensionHostPort === 'number') {
|
||||
return findFreePort(extensionHostPort, 10 /* try 10 ports */, 5000 /* try up to 5 seconds */, (port) => {
|
||||
private resolveDebugPort(extensionHostPort: number): TPromise<number> {
|
||||
if (typeof extensionHostPort !== 'number') {
|
||||
return TPromise.wrap(void 0);
|
||||
}
|
||||
return new TPromise<number>((c, e) => {
|
||||
findFreePort(extensionHostPort, 10 /* try 10 ports */, 5000 /* try up to 5 seconds */, (port) => {
|
||||
if (!port) {
|
||||
console.warn('%c[Extension Host] %cCould not find a free port for debugging', 'color: blue', 'color: black');
|
||||
|
||||
return clb(void 0);
|
||||
c(void 0);
|
||||
}
|
||||
|
||||
if (port !== extensionHostPort) {
|
||||
console.warn('%c[Extension Host] %cProvided debugging port ' + extensionHostPort + ' is not free, using ' + port + ' instead.', 'color: blue', 'color: black');
|
||||
}
|
||||
|
||||
if (this.isExtensionDevelopmentDebugging) {
|
||||
console.warn('%c[Extension Host] %cSTOPPED on first line for debugging on port ' + port, 'color: blue', 'color: black');
|
||||
} else {
|
||||
console.info('%c[Extension Host] %cdebugger listening on port ' + port, 'color: blue', 'color: black');
|
||||
}
|
||||
|
||||
return clb(port);
|
||||
return c(port);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// @return `true` if ready
|
||||
private onMessaage(msg : any, onExtensionHostMessage : (msg: any) => void): boolean {
|
||||
// 1) Host is ready to receive messages, initialize it
|
||||
if (msg === 'ready') {
|
||||
this.initializeExtensionHost();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Nothing to do here
|
||||
else {
|
||||
return clb(void 0);
|
||||
// 2) Host is initialized
|
||||
if (msg === 'initialized') {
|
||||
this.unsentMessages.forEach(m => this.postMessage(m));
|
||||
this.unsentMessages = [];
|
||||
this.extensionHostProcessReady = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Support logging from extension host
|
||||
if (msg && (<ILogEntry>msg).type === '__$console') {
|
||||
this.logExtensionHostMessage(<ILogEntry>msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Any other message goes to the callback
|
||||
onExtensionHostMessage(msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
private initializeExtensionHost() {
|
||||
if (this.initializeTimer) {
|
||||
window.clearTimeout(this.initializeTimer);
|
||||
}
|
||||
|
||||
let initPayload = stringify({
|
||||
parentPid: process.pid,
|
||||
environment: {
|
||||
appSettingsHome: this.environmentService.appSettingsHome,
|
||||
disableExtensions: this.environmentService.disableExtensions,
|
||||
userExtensionsHome: this.environmentService.extensionsPath,
|
||||
extensionDevelopmentPath: this.environmentService.extensionDevelopmentPath,
|
||||
extensionTestsPath: this.environmentService.extensionTestsPath
|
||||
},
|
||||
contextService: {
|
||||
workspace: this.contextService.getWorkspace()
|
||||
}
|
||||
});
|
||||
|
||||
this.extensionHostProcessHandle.send(initPayload);
|
||||
}
|
||||
|
||||
private logExtensionHostMessage(logEntry: ILogEntry) {
|
||||
let args = [];
|
||||
try {
|
||||
let parsed = JSON.parse(logEntry.arguments);
|
||||
args.push(...Object.getOwnPropertyNames(parsed).map(o => parsed[o]));
|
||||
} catch (error) {
|
||||
args.push(logEntry.arguments);
|
||||
}
|
||||
|
||||
// If the first argument is a string, check for % which indicates that the message
|
||||
// uses substitution for variables. In this case, we cannot just inject our colored
|
||||
// [Extension Host] to the front because it breaks substitution.
|
||||
let consoleArgs = [];
|
||||
if (typeof args[0] === 'string' && args[0].indexOf('%') >= 0) {
|
||||
consoleArgs = [`%c[Extension Host]%c ${args[0]}`, 'color: blue', 'color: black', ...args.slice(1)];
|
||||
} else {
|
||||
consoleArgs = ['%c[Extension Host]', 'color: blue', ...args];
|
||||
}
|
||||
|
||||
// Send to local console unless we run tests from cli
|
||||
if (!this.isExtensionDevelopmentTestFromCli) {
|
||||
console[logEntry.severity].apply(console, consoleArgs);
|
||||
}
|
||||
|
||||
// Log on main side if running tests from cli
|
||||
if (this.isExtensionDevelopmentTestFromCli) {
|
||||
ipc.send('vscode:log', logEntry);
|
||||
}
|
||||
|
||||
// Broadcast to other windows if we are in development mode
|
||||
else if (!this.environmentService.isBuilt || this.isExtensionDevelopmentHost) {
|
||||
this.windowService.broadcast({
|
||||
channel: EXTENSION_LOG_BROADCAST_CHANNEL,
|
||||
payload: logEntry
|
||||
}, this.environmentService.extensionDevelopmentPath /* target */);
|
||||
}
|
||||
}
|
||||
|
||||
private onError(err: any): void {
|
||||
let errorMessage = toErrorMessage(err);
|
||||
if (errorMessage === this.lastExtensionHostError) {
|
||||
return; // prevent error spam
|
||||
}
|
||||
|
||||
this.lastExtensionHostError = errorMessage;
|
||||
|
||||
this.messageService.show(Severity.Error, nls.localize('extensionHostProcess.error', "Error from the extension host: {0}", errorMessage));
|
||||
}
|
||||
|
||||
private onExit(code: any, signal: any, onProcessExit: any): void {
|
||||
process.removeListener('exit', onProcessExit);
|
||||
|
||||
if (!this.terminating) {
|
||||
|
||||
// Unexpected termination
|
||||
if (!this.isExtensionDevelopmentHost) {
|
||||
this.messageService.show(Severity.Error, {
|
||||
message: nls.localize('extensionHostProcess.crash', "Extension host terminated unexpectedly. Please reload the window to recover."),
|
||||
actions: [this.instantiationService.createInstance(ReloadWindowAction, ReloadWindowAction.ID, ReloadWindowAction.LABEL)]
|
||||
});
|
||||
console.error('Extension host terminated unexpectedly. Code: ', code, ' Signal: ', signal);
|
||||
}
|
||||
|
||||
// Expected development extension termination: When the extension host goes down we also shutdown the window
|
||||
else if (!this.isExtensionDevelopmentTestFromCli) {
|
||||
this.windowService.getWindow().close();
|
||||
}
|
||||
|
||||
// When CLI testing make sure to exit with proper exit code
|
||||
else {
|
||||
ipc.send('vscode:exit', code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user