Merge from vscode e3b9b8eefc062d68ba8a4b6a817162d132f3b533 (#6932)

* Merge from vscode e3b9b8eefc062d68ba8a4b6a817162d132f3b533

* skip failing test

* add comment
This commit is contained in:
Anthony Dresser
2019-08-24 00:19:48 -07:00
committed by GitHub
parent 023d06d114
commit 92a3acbfe8
77 changed files with 992 additions and 559 deletions

View File

@@ -21,6 +21,7 @@ export interface ILabelService {
* If noPrefix is passed does not tildify the label and also does not prepand the root name for relative labels in a multi root scenario.
*/
getUriLabel(resource: URI, options?: { relative?: boolean, noPrefix?: boolean, endWithSeparator?: boolean }): string;
getUriBasenameLabel(resource: URI): string;
getWorkspaceLabel(workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWorkspace), options?: { verbose: boolean }): string;
getHostLabel(scheme: string, authority?: string): string;
getSeparator(scheme: string, authority?: string): '/' | '\\';

View File

@@ -8,7 +8,6 @@ import { ILogService } from 'vs/platform/log/common/log';
import { AbstractLifecycleService } from 'vs/platform/lifecycle/common/lifecycleService';
import { localize } from 'vs/nls';
import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
import { addDisposableListener, EventType } from 'vs/base/browser/dom';
export class BrowserLifecycleService extends AbstractLifecycleService {
@@ -23,7 +22,9 @@ export class BrowserLifecycleService extends AbstractLifecycleService {
}
private registerListeners(): void {
addDisposableListener(window, EventType.BEFORE_UNLOAD, () => this.onBeforeUnload());
// Note: we cannot change this to window.addEventListener('beforeUnload')
// because it seems that mechanism does not allow for preventing the unload
window.onbeforeunload = () => this.onBeforeUnload();
}
private onBeforeUnload(): string | null {

View File

@@ -89,7 +89,6 @@ export interface IProductConfiguration {
readonly 'linux-x64': string;
readonly 'darwin': string;
};
readonly logUploaderUrl: string;
readonly portable?: string;
readonly uiExtensions?: readonly string[];
}

View File

@@ -10,9 +10,10 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { VSBuffer } from 'vs/base/common/buffer';
import { Emitter } from 'vs/base/common/event';
import { RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { isPromiseCanceledError } from 'vs/base/common/errors';
import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors';
import { ISignService } from 'vs/platform/sign/common/sign';
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
import { ILogService } from 'vs/platform/log/common/log';
export const enum ConnectionType {
Management = 1,
@@ -20,6 +21,17 @@ export const enum ConnectionType {
Tunnel = 3,
}
function connectionTypeToString(connectionType: ConnectionType): string {
switch (connectionType) {
case ConnectionType.Management:
return 'Management';
case ConnectionType.ExtensionHost:
return 'ExtensionHost';
case ConnectionType.Tunnel:
return 'Tunnel';
}
}
export interface AuthRequest {
type: 'auth';
auth: string;
@@ -58,6 +70,7 @@ interface ISimpleConnectionOptions {
reconnectionProtocol: PersistentProtocol | null;
socketFactory: ISocketFactory;
signService: ISignService;
logService: ILogService;
}
export interface IConnectCallback {
@@ -68,29 +81,46 @@ export interface ISocketFactory {
connect(host: string, port: number, query: string, callback: IConnectCallback): void;
}
async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptions, connectionType: ConnectionType, args: any | undefined): Promise<PersistentProtocol> {
const protocol = await new Promise<PersistentProtocol>((c, e) => {
async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptions, connectionType: ConnectionType, args: any | undefined): Promise<{ protocol: PersistentProtocol; ownsProtocol: boolean; }> {
const logPrefix = connectLogPrefix(options, connectionType);
const { protocol, ownsProtocol } = await new Promise<{ protocol: PersistentProtocol; ownsProtocol: boolean; }>((c, e) => {
options.logService.trace(`${logPrefix} 1/6. invoking socketFactory.connect().`);
options.socketFactory.connect(
options.host,
options.port,
`reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`,
(err: any, socket: ISocket) => {
if (err) {
options.logService.error(`${logPrefix} socketFactory.connect() failed. Error:`);
options.logService.error(err);
e(err);
return;
}
options.logService.trace(`${logPrefix} 2/6. socketFactory.connect() was successful.`);
if (options.reconnectionProtocol) {
options.reconnectionProtocol.beginAcceptReconnection(socket, null);
c(options.reconnectionProtocol);
c({ protocol: options.reconnectionProtocol, ownsProtocol: false });
} else {
c(new PersistentProtocol(socket, null));
c({ protocol: new PersistentProtocol(socket, null), ownsProtocol: true });
}
}
);
});
return new Promise<PersistentProtocol>((c, e) => {
return new Promise<{ protocol: PersistentProtocol; ownsProtocol: boolean; }>((c, e) => {
const errorTimeoutToken = setTimeout(() => {
const error: any = new Error('handshake timeout');
error.code = 'ETIMEDOUT';
error.syscall = 'connect';
options.logService.error(`${logPrefix} the handshake took longer than 10 seconds. Error:`);
options.logService.error(error);
if (ownsProtocol) {
safeDisposeProtocolAndSocket(protocol);
}
e(error);
}, 10000);
const messageRegistration = protocol.onControlMessage(async raw => {
const msg = <HandshakeMessage>JSON.parse(raw.toString());
@@ -99,11 +129,16 @@ async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptio
const error = getErrorFromMessage(msg);
if (error) {
options.logService.error(`${logPrefix} received error control message when negotiating connection. Error:`);
options.logService.error(error);
if (ownsProtocol) {
safeDisposeProtocolAndSocket(protocol);
}
return e(error);
}
if (msg.type === 'sign') {
options.logService.trace(`${logPrefix} 4/6. received SignRequest control message.`);
const signed = await options.signService.sign(msg.data);
const connTypeRequest: ConnectionTypeRequest = {
type: 'connectionType',
@@ -114,17 +149,22 @@ async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptio
if (args) {
connTypeRequest.args = args;
}
options.logService.trace(`${logPrefix} 5/6. sending ConnectionTypeRequest control message.`);
protocol.sendControl(VSBuffer.fromString(JSON.stringify(connTypeRequest)));
c(protocol);
clearTimeout(errorTimeoutToken);
c({ protocol, ownsProtocol });
} else {
e(new Error('handshake error'));
const error = new Error('handshake error');
options.logService.error(`${logPrefix} received unexpected control message. Error:`);
options.logService.error(error);
if (ownsProtocol) {
safeDisposeProtocolAndSocket(protocol);
}
e(error);
}
});
setTimeout(() => {
e(new Error('handshake timeout'));
}, 2000);
options.logService.trace(`${logPrefix} 3/6. sending AuthRequest control message.`);
// TODO@vs-remote: use real nonce here
const authRequest: AuthRequest = {
type: 'auth',
@@ -138,24 +178,37 @@ interface IManagementConnectionResult {
protocol: PersistentProtocol;
}
async function doConnectRemoteAgentManagement(options: ISimpleConnectionOptions): Promise<IManagementConnectionResult> {
const protocol = await connectToRemoteExtensionHostAgent(options, ConnectionType.Management, undefined);
return new Promise<IManagementConnectionResult>((c, e) => {
async function connectToRemoteExtensionHostAgentAndReadOneMessage(options: ISimpleConnectionOptions, connectionType: ConnectionType, args: any | undefined): Promise<{ protocol: PersistentProtocol; firstMessage: any }> {
const startTime = Date.now();
const logPrefix = connectLogPrefix(options, connectionType);
const { protocol, ownsProtocol } = await connectToRemoteExtensionHostAgent(options, connectionType, args);
return new Promise<{ protocol: PersistentProtocol; firstMessage: any }>((c, e) => {
const registration = protocol.onControlMessage(raw => {
registration.dispose();
const msg = JSON.parse(raw.toString());
const error = getErrorFromMessage(msg);
if (error) {
options.logService.error(`${logPrefix} received error control message when negotiating connection. Error:`);
options.logService.error(error);
if (ownsProtocol) {
safeDisposeProtocolAndSocket(protocol);
}
return e(error);
}
if (options.reconnectionProtocol) {
options.reconnectionProtocol.endAcceptReconnection();
}
c({ protocol });
options.logService.trace(`${logPrefix} 6/6. handshake finished, connection is up and running after ${logElapsed(startTime)}!`);
c({ protocol, firstMessage: msg });
});
});
}
async function doConnectRemoteAgentManagement(options: ISimpleConnectionOptions): Promise<IManagementConnectionResult> {
const { protocol } = await connectToRemoteExtensionHostAgentAndReadOneMessage(options, ConnectionType.Management, undefined);
return { protocol };
}
export interface IRemoteExtensionHostStartParams {
language: string;
debugId?: string;
@@ -170,22 +223,9 @@ interface IExtensionHostConnectionResult {
}
async function doConnectRemoteAgentExtensionHost(options: ISimpleConnectionOptions, startArguments: IRemoteExtensionHostStartParams): Promise<IExtensionHostConnectionResult> {
const protocol = await connectToRemoteExtensionHostAgent(options, ConnectionType.ExtensionHost, startArguments);
return new Promise<IExtensionHostConnectionResult>((c, e) => {
const registration = protocol.onControlMessage(raw => {
registration.dispose();
const msg = JSON.parse(raw.toString());
const error = getErrorFromMessage(msg);
if (error) {
return e(error);
}
const debugPort = msg && msg.debugPort;
if (options.reconnectionProtocol) {
options.reconnectionProtocol.endAcceptReconnection();
}
c({ protocol, debugPort });
});
});
const { protocol, firstMessage } = await connectToRemoteExtensionHostAgentAndReadOneMessage(options, ConnectionType.ExtensionHost, startArguments);
const debugPort = firstMessage && firstMessage.debugPort;
return { protocol, debugPort };
}
export interface ITunnelConnectionStartParams {
@@ -193,7 +233,10 @@ export interface ITunnelConnectionStartParams {
}
async function doConnectRemoteAgentTunnel(options: ISimpleConnectionOptions, startParams: ITunnelConnectionStartParams): Promise<PersistentProtocol> {
const protocol = await connectToRemoteExtensionHostAgent(options, ConnectionType.Tunnel, startParams);
const startTime = Date.now();
const logPrefix = connectLogPrefix(options, ConnectionType.Tunnel);
const { protocol } = await connectToRemoteExtensionHostAgent(options, ConnectionType.Tunnel, startParams);
options.logService.trace(`${logPrefix} 6/6. handshake finished, connection is up and running after ${logElapsed(startTime)}!`);
return protocol;
}
@@ -202,6 +245,7 @@ export interface IConnectionOptions {
socketFactory: ISocketFactory;
addressProvider: IAddressProvider;
signService: ISignService;
logService: ILogService;
}
async function resolveConnectionOptions(options: IConnectionOptions, reconnectionToken: string, reconnectionProtocol: PersistentProtocol | null): Promise<ISimpleConnectionOptions> {
@@ -213,7 +257,8 @@ async function resolveConnectionOptions(options: IConnectionOptions, reconnectio
reconnectionToken: reconnectionToken,
reconnectionProtocol: reconnectionProtocol,
socketFactory: options.socketFactory,
signService: options.signService
signService: options.signService,
logService: options.logService
};
}
@@ -227,22 +272,36 @@ export interface IAddressProvider {
}
export async function connectRemoteAgentManagement(options: IConnectionOptions, remoteAuthority: string, clientId: string): Promise<ManagementPersistentConnection> {
const reconnectionToken = generateUuid();
const simpleOptions = await resolveConnectionOptions(options, reconnectionToken, null);
const { protocol } = await doConnectRemoteAgentManagement(simpleOptions);
return new ManagementPersistentConnection(options, remoteAuthority, clientId, reconnectionToken, protocol);
try {
const reconnectionToken = generateUuid();
const simpleOptions = await resolveConnectionOptions(options, reconnectionToken, null);
const { protocol } = await connectWithTimeLimit(simpleOptions.logService, doConnectRemoteAgentManagement(simpleOptions), 30 * 1000 /*30s*/);
return new ManagementPersistentConnection(options, remoteAuthority, clientId, reconnectionToken, protocol);
} catch (err) {
options.logService.error(`[remote-connection] An error occurred in the very first connect attempt, it will be treated as a permanent error! Error:`);
options.logService.error(err);
PersistentConnection.triggerPermanentFailure();
throw err;
}
}
export async function connectRemoteAgentExtensionHost(options: IConnectionOptions, startArguments: IRemoteExtensionHostStartParams): Promise<ExtensionHostPersistentConnection> {
const reconnectionToken = generateUuid();
const simpleOptions = await resolveConnectionOptions(options, reconnectionToken, null);
const { protocol, debugPort } = await doConnectRemoteAgentExtensionHost(simpleOptions, startArguments);
return new ExtensionHostPersistentConnection(options, startArguments, reconnectionToken, protocol, debugPort);
try {
const reconnectionToken = generateUuid();
const simpleOptions = await resolveConnectionOptions(options, reconnectionToken, null);
const { protocol, debugPort } = await connectWithTimeLimit(simpleOptions.logService, doConnectRemoteAgentExtensionHost(simpleOptions, startArguments), 30 * 1000 /*30s*/);
return new ExtensionHostPersistentConnection(options, startArguments, reconnectionToken, protocol, debugPort);
} catch (err) {
options.logService.error(`[remote-connection] An error occurred in the very first connect attempt, it will be treated as a permanent error! Error:`);
options.logService.error(err);
PersistentConnection.triggerPermanentFailure();
throw err;
}
}
export async function connectRemoteAgentTunnel(options: IConnectionOptions, tunnelRemotePort: number): Promise<PersistentProtocol> {
const simpleOptions = await resolveConnectionOptions(options, generateUuid(), null);
const protocol = await doConnectRemoteAgentTunnel(simpleOptions, { port: tunnelRemotePort });
const protocol = await connectWithTimeLimit(simpleOptions.logService, doConnectRemoteAgentTunnel(simpleOptions, { port: tunnelRemotePort }), 30 * 1000 /*30s*/);
return protocol;
}
@@ -292,6 +351,13 @@ export type PersistenConnectionEvent = ConnectionGainEvent | ConnectionLostEvent
abstract class PersistentConnection extends Disposable {
public static triggerPermanentFailure(): void {
this._permanentFailure = true;
this._instances.forEach(instance => instance._gotoPermanentFailure());
}
private static _permanentFailure: boolean = false;
private static _instances: PersistentConnection[] = [];
private readonly _onDidStateChange = this._register(new Emitter<PersistenConnectionEvent>());
public readonly onDidStateChange = this._onDidStateChange.event;
@@ -300,20 +366,24 @@ abstract class PersistentConnection extends Disposable {
public readonly protocol: PersistentProtocol;
private _isReconnecting: boolean;
private _permanentFailure: boolean;
constructor(options: IConnectionOptions, reconnectionToken: string, protocol: PersistentProtocol) {
constructor(private readonly _connectionType: ConnectionType, options: IConnectionOptions, reconnectionToken: string, protocol: PersistentProtocol) {
super();
this._options = options;
this.reconnectionToken = reconnectionToken;
this.protocol = protocol;
this._isReconnecting = false;
this._permanentFailure = false;
this._onDidStateChange.fire(new ConnectionGainEvent());
this._register(protocol.onSocketClose(() => this._beginReconnecting()));
this._register(protocol.onSocketTimeout(() => this._beginReconnecting()));
PersistentConnection._instances.push(this);
if (PersistentConnection._permanentFailure) {
this._gotoPermanentFailure();
}
}
private async _beginReconnecting(): Promise<void> {
@@ -330,10 +400,12 @@ abstract class PersistentConnection extends Disposable {
}
private async _runReconnectingLoop(): Promise<void> {
if (this._permanentFailure) {
if (PersistentConnection._permanentFailure) {
// no more attempts!
return;
}
const logPrefix = commonLogPrefix(this._connectionType, this.reconnectionToken, true);
this._options.logService.info(`${logPrefix} starting reconnecting loop. You can get more information with the trace log level.`);
this._onDidStateChange.fire(new ConnectionLostEvent());
const TIMES = [5, 5, 10, 10, 10, 10, 10, 30];
const disconnectStartTime = Date.now();
@@ -345,59 +417,68 @@ abstract class PersistentConnection extends Disposable {
const sleepPromise = sleep(waitTime);
this._onDidStateChange.fire(new ReconnectionWaitEvent(waitTime, sleepPromise));
this._options.logService.info(`${logPrefix} waiting for ${waitTime} seconds before reconnecting...`);
try {
await sleepPromise;
} catch { } // User canceled timer
if (PersistentConnection._permanentFailure) {
this._options.logService.error(`${logPrefix} permanent failure occurred while running the reconnecting loop.`);
break;
}
// connection was lost, let's try to re-establish it
this._onDidStateChange.fire(new ReconnectionRunningEvent());
this._options.logService.info(`${logPrefix} resolving connection...`);
const simpleOptions = await resolveConnectionOptions(this._options, this.reconnectionToken, this.protocol);
await connectWithTimeLimit(this._reconnect(simpleOptions), 30 * 1000 /*30s*/);
this._options.logService.info(`${logPrefix} connecting to ${simpleOptions.host}:${simpleOptions.port}...`);
await connectWithTimeLimit(simpleOptions.logService, this._reconnect(simpleOptions), 30 * 1000 /*30s*/);
this._options.logService.info(`${logPrefix} reconnected!`);
this._onDidStateChange.fire(new ConnectionGainEvent());
break;
} catch (err) {
if (err.code === 'VSCODE_CONNECTION_ERROR') {
console.error(`A permanent connection error occurred`);
console.error(err);
this._permanentFailure = true;
this._onDidStateChange.fire(new ReconnectionPermanentFailureEvent());
this.protocol.acceptDisconnect();
this._options.logService.error(`${logPrefix} A permanent error occurred in the reconnecting loop! Will give up now! Error:`);
this._options.logService.error(err);
PersistentConnection.triggerPermanentFailure();
break;
}
if (Date.now() - disconnectStartTime > ProtocolConstants.ReconnectionGraceTime) {
console.error(`Giving up after reconnection grace time has expired!`);
this._permanentFailure = true;
this._onDidStateChange.fire(new ReconnectionPermanentFailureEvent());
this.protocol.acceptDisconnect();
this._options.logService.error(`${logPrefix} An error occurred while reconnecting, but it will be treated as a permanent error because the reconnection grace time has expired! Will give up now! Error:`);
this._options.logService.error(err);
PersistentConnection.triggerPermanentFailure();
break;
}
if (RemoteAuthorityResolverError.isTemporarilyNotAvailable(err)) {
console.warn(`A temporarily not available error occured while trying to reconnect:`);
console.warn(err);
this._options.logService.info(`${logPrefix} A temporarily not available error occured while trying to reconnect, will try again...`);
this._options.logService.trace(err);
// try again!
continue;
}
if ((err.code === 'ETIMEDOUT' || err.code === 'ENETUNREACH' || err.code === 'ECONNREFUSED' || err.code === 'ECONNRESET') && err.syscall === 'connect') {
console.warn(`A connect error occured while trying to reconnect:`);
console.warn(err);
this._options.logService.info(`${logPrefix} A network error occured while trying to reconnect, will try again...`);
this._options.logService.trace(err);
// try again!
continue;
}
if (isPromiseCanceledError(err)) {
console.warn(`A cancel error occured while trying to reconnect:`);
console.warn(err);
this._options.logService.info(`${logPrefix} A promise cancelation error occured while trying to reconnect, will try again...`);
this._options.logService.trace(err);
// try again!
continue;
}
console.error(`An error occured while trying to reconnect:`);
console.error(err);
this._permanentFailure = true;
this._onDidStateChange.fire(new ReconnectionPermanentFailureEvent());
this.protocol.acceptDisconnect();
this._options.logService.error(`${logPrefix} An unknown error occured while trying to reconnect, since this is an unknown case, it will be treated as a permanent error! Will give up now! Error:`);
this._options.logService.error(err);
PersistentConnection.triggerPermanentFailure();
break;
}
} while (!this._permanentFailure);
} while (!PersistentConnection._permanentFailure);
}
private _gotoPermanentFailure(): void {
this._onDidStateChange.fire(new ReconnectionPermanentFailureEvent());
safeDisposeProtocolAndSocket(this.protocol);
}
protected abstract _reconnect(options: ISimpleConnectionOptions): Promise<void>;
@@ -408,7 +489,7 @@ export class ManagementPersistentConnection extends PersistentConnection {
public readonly client: Client<RemoteAgentConnectionContext>;
constructor(options: IConnectionOptions, remoteAuthority: string, clientId: string, reconnectionToken: string, protocol: PersistentProtocol) {
super(options, reconnectionToken, protocol);
super(ConnectionType.Management, options, reconnectionToken, protocol);
this.client = this._register(new Client<RemoteAgentConnectionContext>(protocol, {
remoteAuthority: remoteAuthority,
clientId: clientId
@@ -426,7 +507,7 @@ export class ExtensionHostPersistentConnection extends PersistentConnection {
public readonly debugPort: number | undefined;
constructor(options: IConnectionOptions, startArguments: IRemoteExtensionHostStartParams, reconnectionToken: string, protocol: PersistentProtocol, debugPort: number | undefined) {
super(options, reconnectionToken, protocol);
super(ConnectionType.ExtensionHost, options, reconnectionToken, protocol);
this._startArguments = startArguments;
this.debugPort = debugPort;
}
@@ -436,17 +517,19 @@ export class ExtensionHostPersistentConnection extends PersistentConnection {
}
}
function connectWithTimeLimit(p: Promise<void>, timeLimit: number): Promise<void> {
return new Promise<void>((resolve, reject) => {
function connectWithTimeLimit<T>(logService: ILogService, p: Promise<T>, timeLimit: number): Promise<T> {
return new Promise<T>((resolve, reject) => {
let timeout = setTimeout(() => {
const err: any = new Error('Time limit reached');
err.code = 'ETIMEDOUT';
err.syscall = 'connect';
logService.error(`[remote-connection] The time limit has been reached for a connection. Error:`);
logService.error(err);
reject(err);
}, timeLimit);
p.then(() => {
p.then((value) => {
clearTimeout(timeout);
resolve();
resolve(value);
}, (err) => {
clearTimeout(timeout);
reject(err);
@@ -454,6 +537,17 @@ function connectWithTimeLimit(p: Promise<void>, timeLimit: number): Promise<void
});
}
function safeDisposeProtocolAndSocket(protocol: PersistentProtocol): void {
try {
protocol.acceptDisconnect();
const socket = protocol.getSocket();
protocol.dispose();
socket.dispose();
} catch (err) {
onUnexpectedError(err);
}
}
function getErrorFromMessage(msg: any): Error | null {
if (msg && msg.type === 'error') {
const error = new Error(`Connection error: ${msg.reason}`);
@@ -462,3 +556,22 @@ function getErrorFromMessage(msg: any): Error | null {
}
return null;
}
function stringRightPad(str: string, len: number): string {
while (str.length < len) {
str += ' ';
}
return str;
}
function commonLogPrefix(connectionType: ConnectionType, reconnectionToken: string, isReconnect: boolean): string {
return `[remote-connection][${stringRightPad(connectionTypeToString(connectionType), 13)}][${reconnectionToken.substr(0, 5)}…][${isReconnect ? 'reconnect' : 'initial'}]`;
}
function connectLogPrefix(options: ISimpleConnectionOptions, connectionType: ConnectionType): string {
return `${commonLogPrefix(connectionType, options.reconnectionToken, !!options.reconnectionProtocol)}[${options.host}:${options.port}]`;
}
function logElapsed(startTime: number): string {
return `${Date.now() - startTime} ms`;
}