Skip to content

Commit

Permalink
Clean up thred service
Browse files Browse the repository at this point in the history
  • Loading branch information
sandy081 committed Oct 7, 2016
1 parent cf6b4b5 commit 8a6b8fd
Showing 1 changed file with 146 additions and 138 deletions.
284 changes: 146 additions & 138 deletions src/vs/workbench/services/thread/electron-browser/threadService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand All @@ -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;
}

// 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);
}

// Nothing to do here
else {
return clb(void 0);
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);
}
}
}

Expand Down

1 comment on commit 8a6b8fd

@sandy081
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.