mirror of
https://github.com/coder/code-server.git
synced 2026-05-05 20:15:19 +02:00
chore(vscode): update to 1.54.2
This commit is contained in:
@@ -6,15 +6,16 @@
|
||||
import { Client, PersistentProtocol, ISocket, ProtocolConstants } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
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';
|
||||
import { IIPCLogger } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
|
||||
const INITIAL_CONNECT_TIMEOUT = 120 * 1000 /* 120s */;
|
||||
const RECONNECT_TIMEOUT = 30 * 1000 /* 30s */;
|
||||
@@ -86,59 +87,152 @@ export interface ISocketFactory {
|
||||
connect(host: string, port: number, query: string, callback: IConnectCallback): void;
|
||||
}
|
||||
|
||||
async function readOneControlMessage<T>(protocol: PersistentProtocol): Promise<T> {
|
||||
const raw = await Event.toPromise(protocol.onControlMessage);
|
||||
const msg = JSON.parse(raw.toString());
|
||||
const error = getErrorFromMessage(msg);
|
||||
if (error) {
|
||||
throw error;
|
||||
function createTimeoutCancellation(millis: number): CancellationToken {
|
||||
const source = new CancellationTokenSource();
|
||||
setTimeout(() => source.cancel(), millis);
|
||||
return source.token;
|
||||
}
|
||||
|
||||
function combineTimeoutCancellation(a: CancellationToken, b: CancellationToken): CancellationToken {
|
||||
if (a.isCancellationRequested || b.isCancellationRequested) {
|
||||
return CancellationToken.Cancelled;
|
||||
}
|
||||
return msg;
|
||||
const source = new CancellationTokenSource();
|
||||
a.onCancellationRequested(() => source.cancel());
|
||||
b.onCancellationRequested(() => source.cancel());
|
||||
return source.token;
|
||||
}
|
||||
|
||||
function waitWithTimeout<T>(promise: Promise<T>, timeout: number): Promise<T> {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const timeoutToken = setTimeout(() => {
|
||||
const error: any = new Error('Timeout');
|
||||
error.code = 'ETIMEDOUT';
|
||||
error.syscall = 'connect';
|
||||
reject(error);
|
||||
}, timeout);
|
||||
class PromiseWithTimeout<T> {
|
||||
|
||||
promise.then(
|
||||
(result) => {
|
||||
clearTimeout(timeoutToken);
|
||||
resolve(result);
|
||||
},
|
||||
(error) => {
|
||||
clearTimeout(timeoutToken);
|
||||
reject(error);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
private _state: 'pending' | 'resolved' | 'rejected' | 'timedout';
|
||||
private readonly _disposables: DisposableStore;
|
||||
public readonly promise: Promise<T>;
|
||||
private _resolvePromise!: (value: T) => void;
|
||||
private _rejectPromise!: (err: any) => void;
|
||||
|
||||
function createSocket(socketFactory: ISocketFactory, host: string, port: number, query: string): Promise<ISocket> {
|
||||
return new Promise<ISocket>((resolve, reject) => {
|
||||
socketFactory.connect(host, port, query, (err: any, socket: ISocket | undefined) => {
|
||||
if (err || !socket) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(socket);
|
||||
public get didTimeout(): boolean {
|
||||
return (this._state === 'timedout');
|
||||
}
|
||||
|
||||
constructor(timeoutCancellationToken: CancellationToken) {
|
||||
this._state = 'pending';
|
||||
this._disposables = new DisposableStore();
|
||||
this.promise = new Promise<T>((resolve, reject) => {
|
||||
this._resolvePromise = resolve;
|
||||
this._rejectPromise = reject;
|
||||
});
|
||||
});
|
||||
|
||||
if (timeoutCancellationToken.isCancellationRequested) {
|
||||
this._timeout();
|
||||
} else {
|
||||
this._disposables.add(timeoutCancellationToken.onCancellationRequested(() => this._timeout()));
|
||||
}
|
||||
}
|
||||
|
||||
public registerDisposable(disposable: IDisposable): void {
|
||||
if (this._state === 'pending') {
|
||||
this._disposables.add(disposable);
|
||||
} else {
|
||||
disposable.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private _timeout(): void {
|
||||
if (this._state !== 'pending') {
|
||||
return;
|
||||
}
|
||||
this._disposables.dispose();
|
||||
this._state = 'timedout';
|
||||
this._rejectPromise(this._createTimeoutError());
|
||||
}
|
||||
|
||||
private _createTimeoutError(): Error {
|
||||
const err: any = new Error('Time limit reached');
|
||||
err.code = 'ETIMEDOUT';
|
||||
err.syscall = 'connect';
|
||||
return err;
|
||||
}
|
||||
|
||||
public resolve(value: T): void {
|
||||
if (this._state !== 'pending') {
|
||||
return;
|
||||
}
|
||||
this._disposables.dispose();
|
||||
this._state = 'resolved';
|
||||
this._resolvePromise(value);
|
||||
}
|
||||
|
||||
public reject(err: any): void {
|
||||
if (this._state !== 'pending') {
|
||||
return;
|
||||
}
|
||||
this._disposables.dispose();
|
||||
this._state = 'rejected';
|
||||
this._rejectPromise(err);
|
||||
}
|
||||
}
|
||||
|
||||
async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptions, connectionType: ConnectionType, args: any | undefined): Promise<{ protocol: PersistentProtocol; ownsProtocol: boolean; }> {
|
||||
function readOneControlMessage<T>(protocol: PersistentProtocol, timeoutCancellationToken: CancellationToken): Promise<T> {
|
||||
const result = new PromiseWithTimeout<T>(timeoutCancellationToken);
|
||||
result.registerDisposable(protocol.onControlMessage(raw => {
|
||||
const msg: T = JSON.parse(raw.toString());
|
||||
const error = getErrorFromMessage(msg);
|
||||
if (error) {
|
||||
result.reject(error);
|
||||
} else {
|
||||
result.resolve(msg);
|
||||
}
|
||||
}));
|
||||
return result.promise;
|
||||
}
|
||||
|
||||
function createSocket(logService: ILogService, socketFactory: ISocketFactory, host: string, port: number, query: string, timeoutCancellationToken: CancellationToken): Promise<ISocket> {
|
||||
const result = new PromiseWithTimeout<ISocket>(timeoutCancellationToken);
|
||||
socketFactory.connect(host, port, query, (err: any, socket: ISocket | undefined) => {
|
||||
if (result.didTimeout) {
|
||||
if (err) {
|
||||
logService.error(err);
|
||||
}
|
||||
socket?.dispose();
|
||||
} else {
|
||||
if (err || !socket) {
|
||||
result.reject(err);
|
||||
} else {
|
||||
result.resolve(socket);
|
||||
}
|
||||
}
|
||||
});
|
||||
return result.promise;
|
||||
}
|
||||
|
||||
function raceWithTimeoutCancellation<T>(promise: Promise<T>, timeoutCancellationToken: CancellationToken): Promise<T> {
|
||||
const result = new PromiseWithTimeout<T>(timeoutCancellationToken);
|
||||
promise.then(
|
||||
(res) => {
|
||||
if (!result.didTimeout) {
|
||||
result.resolve(res);
|
||||
}
|
||||
},
|
||||
(err) => {
|
||||
if (!result.didTimeout) {
|
||||
result.reject(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
return result.promise;
|
||||
}
|
||||
|
||||
async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptions, connectionType: ConnectionType, args: any | undefined, timeoutCancellationToken: CancellationToken): Promise<{ protocol: PersistentProtocol; ownsProtocol: boolean; }> {
|
||||
const logPrefix = connectLogPrefix(options, connectionType);
|
||||
|
||||
options.logService.trace(`${logPrefix} 1/6. invoking socketFactory.connect().`);
|
||||
|
||||
let socket: ISocket;
|
||||
try {
|
||||
socket = await createSocket(options.socketFactory, options.host, options.port, `reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`);
|
||||
socket = await createSocket(options.logService, options.socketFactory, options.host, options.port, `reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`, timeoutCancellationToken);
|
||||
} catch (error) {
|
||||
options.logService.error(`${logPrefix} socketFactory.connect() failed. Error:`);
|
||||
options.logService.error(`${logPrefix} socketFactory.connect() failed or timed out. Error:`);
|
||||
options.logService.error(error);
|
||||
throw error;
|
||||
}
|
||||
@@ -164,7 +258,7 @@ async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptio
|
||||
protocol.sendControl(VSBuffer.fromString(JSON.stringify(authRequest)));
|
||||
|
||||
try {
|
||||
const msg = await waitWithTimeout(readOneControlMessage<HandshakeMessage>(protocol), 10000);
|
||||
const msg = await readOneControlMessage<HandshakeMessage>(protocol, combineTimeoutCancellation(timeoutCancellationToken, createTimeoutCancellation(10000)));
|
||||
|
||||
if (msg.type !== 'sign' || typeof msg.data !== 'string') {
|
||||
const error: any = new Error('Unexpected handshake message');
|
||||
@@ -174,7 +268,7 @@ async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptio
|
||||
|
||||
options.logService.trace(`${logPrefix} 4/6. received SignRequest control message.`);
|
||||
|
||||
const signed = await options.signService.sign(msg.data);
|
||||
const signed = await raceWithTimeoutCancellation(options.signService.sign(msg.data), timeoutCancellationToken);
|
||||
const connTypeRequest: ConnectionTypeRequest = {
|
||||
type: 'connectionType',
|
||||
commit: options.commit,
|
||||
@@ -192,7 +286,7 @@ async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptio
|
||||
|
||||
} catch (error) {
|
||||
if (error && error.code === 'ETIMEDOUT') {
|
||||
options.logService.error(`${logPrefix} the handshake took longer than 10 seconds. Error:`);
|
||||
options.logService.error(`${logPrefix} the handshake timed out. Error:`);
|
||||
options.logService.error(error);
|
||||
}
|
||||
if (error && error.code === 'VSCODE_CONNECTION_ERROR') {
|
||||
@@ -210,34 +304,34 @@ interface IManagementConnectionResult {
|
||||
protocol: PersistentProtocol;
|
||||
}
|
||||
|
||||
async function connectToRemoteExtensionHostAgentAndReadOneMessage(options: ISimpleConnectionOptions, connectionType: ConnectionType, args: any | undefined): Promise<{ protocol: PersistentProtocol; firstMessage: any }> {
|
||||
async function connectToRemoteExtensionHostAgentAndReadOneMessage<T>(options: ISimpleConnectionOptions, connectionType: ConnectionType, args: any | undefined, timeoutCancellationToken: CancellationToken): Promise<{ protocol: PersistentProtocol; firstMessage: T; }> {
|
||||
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);
|
||||
const { protocol, ownsProtocol } = await connectToRemoteExtensionHostAgent(options, connectionType, args, timeoutCancellationToken);
|
||||
const result = new PromiseWithTimeout<{ protocol: PersistentProtocol; firstMessage: T; }>(timeoutCancellationToken);
|
||||
result.registerDisposable(protocol.onControlMessage(raw => {
|
||||
const msg: T = 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);
|
||||
}
|
||||
result.reject(error);
|
||||
} else {
|
||||
if (options.reconnectionProtocol) {
|
||||
options.reconnectionProtocol.endAcceptReconnection();
|
||||
}
|
||||
options.logService.trace(`${logPrefix} 6/6. handshake finished, connection is up and running after ${logElapsed(startTime)}!`);
|
||||
c({ protocol, firstMessage: msg });
|
||||
});
|
||||
});
|
||||
result.resolve({ protocol, firstMessage: msg });
|
||||
}
|
||||
}));
|
||||
return result.promise;
|
||||
}
|
||||
|
||||
async function doConnectRemoteAgentManagement(options: ISimpleConnectionOptions): Promise<IManagementConnectionResult> {
|
||||
const { protocol } = await connectToRemoteExtensionHostAgentAndReadOneMessage(options, ConnectionType.Management, undefined);
|
||||
async function doConnectRemoteAgentManagement(options: ISimpleConnectionOptions, timeoutCancellationToken: CancellationToken): Promise<IManagementConnectionResult> {
|
||||
const { protocol } = await connectToRemoteExtensionHostAgentAndReadOneMessage(options, ConnectionType.Management, undefined, timeoutCancellationToken);
|
||||
return { protocol };
|
||||
}
|
||||
|
||||
@@ -254,8 +348,8 @@ interface IExtensionHostConnectionResult {
|
||||
debugPort?: number;
|
||||
}
|
||||
|
||||
async function doConnectRemoteAgentExtensionHost(options: ISimpleConnectionOptions, startArguments: IRemoteExtensionHostStartParams): Promise<IExtensionHostConnectionResult> {
|
||||
const { protocol, firstMessage } = await connectToRemoteExtensionHostAgentAndReadOneMessage(options, ConnectionType.ExtensionHost, startArguments);
|
||||
async function doConnectRemoteAgentExtensionHost(options: ISimpleConnectionOptions, startArguments: IRemoteExtensionHostStartParams, timeoutCancellationToken: CancellationToken): Promise<IExtensionHostConnectionResult> {
|
||||
const { protocol, firstMessage } = await connectToRemoteExtensionHostAgentAndReadOneMessage<{ debugPort?: number; }>(options, ConnectionType.ExtensionHost, startArguments, timeoutCancellationToken);
|
||||
const debugPort = firstMessage && firstMessage.debugPort;
|
||||
return { protocol, debugPort };
|
||||
}
|
||||
@@ -264,10 +358,10 @@ export interface ITunnelConnectionStartParams {
|
||||
port: number;
|
||||
}
|
||||
|
||||
async function doConnectRemoteAgentTunnel(options: ISimpleConnectionOptions, startParams: ITunnelConnectionStartParams): Promise<PersistentProtocol> {
|
||||
async function doConnectRemoteAgentTunnel(options: ISimpleConnectionOptions, startParams: ITunnelConnectionStartParams, timeoutCancellationToken: CancellationToken): Promise<PersistentProtocol> {
|
||||
const startTime = Date.now();
|
||||
const logPrefix = connectLogPrefix(options, ConnectionType.Tunnel);
|
||||
const { protocol } = await connectToRemoteExtensionHostAgent(options, ConnectionType.Tunnel, startParams);
|
||||
const { protocol } = await connectToRemoteExtensionHostAgent(options, ConnectionType.Tunnel, startParams, timeoutCancellationToken);
|
||||
options.logService.trace(`${logPrefix} 6/6. handshake finished, connection is up and running after ${logElapsed(startTime)}!`);
|
||||
return protocol;
|
||||
}
|
||||
@@ -310,7 +404,7 @@ export async function connectRemoteAgentManagement(options: IConnectionOptions,
|
||||
try {
|
||||
const reconnectionToken = generateUuid();
|
||||
const simpleOptions = await resolveConnectionOptions(options, reconnectionToken, null);
|
||||
const { protocol } = await connectWithTimeLimit(simpleOptions.logService, doConnectRemoteAgentManagement(simpleOptions), INITIAL_CONNECT_TIMEOUT);
|
||||
const { protocol } = await doConnectRemoteAgentManagement(simpleOptions, createTimeoutCancellation(INITIAL_CONNECT_TIMEOUT));
|
||||
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:`);
|
||||
@@ -324,7 +418,7 @@ export async function connectRemoteAgentExtensionHost(options: IConnectionOption
|
||||
try {
|
||||
const reconnectionToken = generateUuid();
|
||||
const simpleOptions = await resolveConnectionOptions(options, reconnectionToken, null);
|
||||
const { protocol, debugPort } = await connectWithTimeLimit(simpleOptions.logService, doConnectRemoteAgentExtensionHost(simpleOptions, startArguments), INITIAL_CONNECT_TIMEOUT);
|
||||
const { protocol, debugPort } = await doConnectRemoteAgentExtensionHost(simpleOptions, startArguments, createTimeoutCancellation(INITIAL_CONNECT_TIMEOUT));
|
||||
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:`);
|
||||
@@ -336,7 +430,7 @@ export async function connectRemoteAgentExtensionHost(options: IConnectionOption
|
||||
|
||||
export async function connectRemoteAgentTunnel(options: IConnectionOptions, tunnelRemotePort: number): Promise<PersistentProtocol> {
|
||||
const simpleOptions = await resolveConnectionOptions(options, generateUuid(), null);
|
||||
const protocol = await connectWithTimeLimit(simpleOptions.logService, doConnectRemoteAgentTunnel(simpleOptions, { port: tunnelRemotePort }), INITIAL_CONNECT_TIMEOUT);
|
||||
const protocol = await doConnectRemoteAgentTunnel(simpleOptions, { port: tunnelRemotePort }, createTimeoutCancellation(INITIAL_CONNECT_TIMEOUT));
|
||||
return protocol;
|
||||
}
|
||||
|
||||
@@ -497,7 +591,7 @@ abstract class PersistentConnection extends Disposable {
|
||||
this._options.logService.info(`${logPrefix} resolving connection...`);
|
||||
const simpleOptions = await resolveConnectionOptions(this._options, this.reconnectionToken, this.protocol);
|
||||
this._options.logService.info(`${logPrefix} connecting to ${simpleOptions.host}:${simpleOptions.port}...`);
|
||||
await connectWithTimeLimit(simpleOptions.logService, this._reconnect(simpleOptions), RECONNECT_TIMEOUT);
|
||||
await this._reconnect(simpleOptions, createTimeoutCancellation(RECONNECT_TIMEOUT));
|
||||
this._options.logService.info(`${logPrefix} reconnected!`);
|
||||
this._onDidStateChange.fire(new ConnectionGainEvent(this.reconnectionToken, this.protocol.getMillisSinceLastIncomingData(), attempt + 1));
|
||||
|
||||
@@ -552,7 +646,7 @@ abstract class PersistentConnection extends Disposable {
|
||||
safeDisposeProtocolAndSocket(this.protocol);
|
||||
}
|
||||
|
||||
protected abstract _reconnect(options: ISimpleConnectionOptions): Promise<void>;
|
||||
protected abstract _reconnect(options: ISimpleConnectionOptions, timeoutCancellationToken: CancellationToken): Promise<void>;
|
||||
}
|
||||
|
||||
export class ManagementPersistentConnection extends PersistentConnection {
|
||||
@@ -567,8 +661,8 @@ export class ManagementPersistentConnection extends PersistentConnection {
|
||||
}, options.ipcLogger));
|
||||
}
|
||||
|
||||
protected async _reconnect(options: ISimpleConnectionOptions): Promise<void> {
|
||||
await doConnectRemoteAgentManagement(options);
|
||||
protected async _reconnect(options: ISimpleConnectionOptions, timeoutCancellationToken: CancellationToken): Promise<void> {
|
||||
await doConnectRemoteAgentManagement(options, timeoutCancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -583,31 +677,11 @@ export class ExtensionHostPersistentConnection extends PersistentConnection {
|
||||
this.debugPort = debugPort;
|
||||
}
|
||||
|
||||
protected async _reconnect(options: ISimpleConnectionOptions): Promise<void> {
|
||||
await doConnectRemoteAgentExtensionHost(options, this._startArguments);
|
||||
protected async _reconnect(options: ISimpleConnectionOptions, timeoutCancellationToken: CancellationToken): Promise<void> {
|
||||
await doConnectRemoteAgentExtensionHost(options, this._startArguments, timeoutCancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
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((value) => {
|
||||
clearTimeout(timeout);
|
||||
resolve(value);
|
||||
}, (err) => {
|
||||
clearTimeout(timeout);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function safeDisposeProtocolAndSocket(protocol: PersistentProtocol): void {
|
||||
try {
|
||||
protocol.acceptDisconnect();
|
||||
|
||||
Reference in New Issue
Block a user