Show an error when resolving shell environment fails or is timing out (fix #133910)

上级 04d76301
......@@ -7,6 +7,7 @@ import { app, BrowserWindow, contentTracing, dialog, ipcMain, protocol, session,
import { statSync } from 'fs';
import { hostname, release } from 'os';
import { VSBuffer } from 'vs/base/common/buffer';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors';
import { isEqualOrParent } from 'vs/base/common/extpath';
import { once } from 'vs/base/common/functional';
......@@ -279,7 +280,7 @@ export class CodeApplication extends Disposable {
}
// Resolve shell env
return resolveShellEnv(this.logService, args, env);
return this.resolveShellEnvironment(args, env);
});
ipcMain.handle('vscode:writeNlsFile', (event, path: unknown, data: unknown) => {
......@@ -978,7 +979,8 @@ export class CodeApplication extends Disposable {
// Start to fetch shell environment (if needed) after window has opened
// Since this operation can take a long time, we want to warm it up while
// the window is opening.
resolveShellEnv(this.logService, this.environmentMainService.args, process.env);
// We also show an error to the user in case this fails.
this.resolveShellEnvironment(this.environmentMainService.args, process.env);
// If enable-crash-reporter argv is undefined then this is a fresh start,
// based on telemetry.enableCrashreporter settings, generate a UUID which
......@@ -1011,6 +1013,16 @@ export class CodeApplication extends Disposable {
}
}
private async resolveShellEnvironment(args: NativeParsedArgs, env: IProcessEnvironment): Promise<typeof process.env> {
try {
return await resolveShellEnv(this.logService, args, env);
} catch (error) {
this.windowsMainService?.sendToFocused('vscode:showResolveShellEnvError', toErrorMessage(error));
}
return {};
}
private stopTracingEventually(accessor: ServicesAccessor, windows: ICodeWindow[]): void {
this.logService.info(`Tracing: waiting for windows to get ready...`);
......
......@@ -3,8 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { basename } from 'path';
import { spawn } from 'child_process';
import * as path from 'path';
import { localize } from 'vs/nls';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { canceled, isPromiseCanceledError } from 'vs/base/common/errors';
......@@ -15,10 +16,23 @@ import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper';
import { ILogService } from 'vs/platform/log/common/log';
/**
* The maximum of time we accept to wait on resolving the shell
* environment before giving up. This ensures we are not blocking
* other tasks from running for a too long time period.
*/
const MAX_SHELL_RESOLVE_TIME = 10000;
let unixShellEnvPromise: Promise<typeof process.env> | undefined = undefined;
/**
* We need to get the environment from a user's shell.
* This should only be done when Code itself is not launched
* from within a shell.
*
* Will throw an error if:
* - we hit a timeout of `MAX_SHELL_RESOLVE_TIME`
* - any other error from spawning a shell to figure out the environment
*/
export async function resolveShellEnv(logService: ILogService, args: NativeParsedArgs, env: IProcessEnvironment): Promise<typeof process.env> {
......@@ -55,16 +69,14 @@ export async function resolveShellEnv(logService: ILogService, args: NativeParse
// subsequent calls since this operation can be
// expensive (spawns a process).
if (!unixShellEnvPromise) {
unixShellEnvPromise = new Promise(async resolve => {
unixShellEnvPromise = new Promise(async (resolve, reject) => {
const cts = new CancellationTokenSource();
// Give up resolving shell env after 10 seconds
// Give up resolving shell env after some time
const timeout = setTimeout(() => {
logService.error(`[resolve shell env] Could not resolve shell environment within 10 seconds. Proceeding without shell environment...`);
cts.dispose(true);
resolve({});
}, 10000);
reject(localize('resolveShellEnvTimeout', "Unable to resolve your shell environment in a reasonable time. Please review your shell configuration."));
}, MAX_SHELL_RESOLVE_TIME);
// Resolve shell env and handle errors
try {
......@@ -72,8 +84,8 @@ export async function resolveShellEnv(logService: ILogService, args: NativeParse
resolve(shellEnv);
} catch (error) {
if (!isPromiseCanceledError(error)) {
logService.error(`[resolve shell env] Unable to resolve shell environment (${error}). Proceeding without shell environment...`);
if (!isPromiseCanceledError(error) && !cts.token.isCancellationRequested) {
reject(localize('resolveShellEnvError', "Unable to resolve your shell environment: {0}", toErrorMessage(error)));
}
resolve({});
......@@ -88,10 +100,8 @@ export async function resolveShellEnv(logService: ILogService, args: NativeParse
}
}
let unixShellEnvPromise: Promise<typeof process.env> | undefined = undefined;
async function doResolveUnixShellEnv(logService: ILogService, token: CancellationToken): Promise<typeof process.env> {
const promise = new Promise<typeof process.env>(async (resolve, reject) => {
return new Promise<typeof process.env>(async (resolve, reject) => {
const runAsNode = process.env['ELECTRON_RUN_AS_NODE'];
logService.trace('getUnixShellEnvironment#runAsNode', runAsNode);
......@@ -116,7 +126,7 @@ async function doResolveUnixShellEnv(logService: ILogService, token: Cancellatio
}
// handle popular non-POSIX shells
const name = path.basename(systemShellUnix);
const name = basename(systemShellUnix);
let command: string, shellArgs: Array<string>;
if (/^pwsh(-preview)?$/.test(name)) {
// Older versions of PowerShell removes double quotes sometimes so we use "double single quotes" which is how
......@@ -144,7 +154,7 @@ async function doResolveUnixShellEnv(logService: ILogService, token: Cancellatio
child.on('error', err => {
logService.error('getUnixShellEnvironment#errorChildProcess', toErrorMessage(err));
resolve({});
reject(err);
});
const buffers: Buffer[] = [];
......@@ -163,7 +173,7 @@ async function doResolveUnixShellEnv(logService: ILogService, token: Cancellatio
}
if (code || signal) {
return reject(new Error(`Failed to get environment (code ${code}, signal ${signal})`));
return reject(new Error(localize('resolveShellEnvExitError', "Unexpected exit code from spawned shell (code {0}, signal {1})", code, signal)));
}
const match = regex.exec(raw);
......@@ -195,12 +205,4 @@ async function doResolveUnixShellEnv(logService: ILogService, token: Cancellatio
}
});
});
try {
return await promise;
} catch (error) {
logService.error('getUnixShellEnvironment#error', toErrorMessage(error));
return {}; // ignore any errors
}
}
......@@ -63,9 +63,17 @@ export class RequestService extends Disposable implements IRequestService {
this.logService.trace('RequestService#request', options.url);
const { proxyUrl, strictSSL } = this;
let shellEnv: typeof process.env | undefined = undefined;
try {
shellEnv = await resolveShellEnv(this.logService, this.environmentService.args, process.env);
} catch (error) {
this.logService.error('RequestService#request resolving shell environment failed', error);
}
const env = {
...process.env,
...(await resolveShellEnv(this.logService, this.environmentService.args, process.env)),
...shellEnv
};
const agent = options.agent ? options.agent : await getProxyAgent(options.url || '', env, { proxyUrl, strictSSL });
......
......@@ -101,7 +101,7 @@ export class PtyHostService extends Disposable implements IPtyService {
// remote server).
registerTerminalPlatformConfiguration();
this._shellEnv = isWindows ? Promise.resolve(process.env) : resolveShellEnv(this._logService, { _: [] }, process.env);
this._shellEnv = this._resolveShellEnv();
this._register(toDisposable(() => this._disposePtyHost()));
......@@ -111,6 +111,20 @@ export class PtyHostService extends Disposable implements IPtyService {
[this._client, this._proxy] = this._startPtyHost();
}
private async _resolveShellEnv(): Promise<typeof process.env> {
if (isWindows) {
return process.env;
}
try {
return await resolveShellEnv(this._logService, { _: [] }, process.env);
} catch (error) {
this._logService.error('ptyHost was unable to resolve shell environment', error);
return {};
}
}
private _startPtyHost(): [Client, IPtyService] {
const opts: IIPCOptions = {
serverName: 'Pty Host',
......
......@@ -186,6 +186,16 @@ export class NativeWindow extends Disposable {
// Message support
ipcRenderer.on('vscode:showInfoMessage', (event: unknown, message: string) => this.notificationService.info(message));
// Shell Environment Issue Notifications
ipcRenderer.on('vscode:showResolveShellEnvError', (event: unknown, message: string) => this.notificationService.prompt(
Severity.Error,
message,
[{
label: localize('learnMore', "Learn More"),
run: () => this.openerService.open('https://go.microsoft.com/fwlink/?linkid=2149667')
}]
));
// Fullscreen Events
ipcRenderer.on('vscode:enterFullScreen', async () => setFullscreen(true));
ipcRenderer.on('vscode:leaveFullScreen', async () => setFullscreen(false));
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册