@sussudio/platform
Version:
Internal APIs for VS Code's service injection the base services.
809 lines (808 loc) • 28 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createCancelablePromise } from '@sussudio/base/common/async.mjs';
import { VSBuffer } from '@sussudio/base/common/buffer.mjs';
import { CancellationToken, CancellationTokenSource } from '@sussudio/base/common/cancellation.mjs';
import { isCancellationError, onUnexpectedError } from '@sussudio/base/common/errors.mjs';
import { Emitter } from '@sussudio/base/common/event.mjs';
import { Disposable, DisposableStore, toDisposable } from '@sussudio/base/common/lifecycle.mjs';
import { generateUuid } from '@sussudio/base/common/uuid.mjs';
import { Client, PersistentProtocol } from '@sussudio/base/parts/ipc/common/ipc.net.mjs';
import { RemoteAuthorityResolverError } from './remoteAuthorityResolver.mjs';
import { getRemoteServerRootPath } from './remoteHosts.mjs';
const RECONNECT_TIMEOUT = 30 * 1000; /* 30s */
function connectionTypeToString(connectionType) {
switch (connectionType) {
case 1 /* ConnectionType.Management */:
return 'Management';
case 2 /* ConnectionType.ExtensionHost */:
return 'ExtensionHost';
case 3 /* ConnectionType.Tunnel */:
return 'Tunnel';
}
}
function createTimeoutCancellation(millis) {
const source = new CancellationTokenSource();
setTimeout(() => source.cancel(), millis);
return source.token;
}
function combineTimeoutCancellation(a, b) {
if (a.isCancellationRequested || b.isCancellationRequested) {
return CancellationToken.Cancelled;
}
const source = new CancellationTokenSource();
a.onCancellationRequested(() => source.cancel());
b.onCancellationRequested(() => source.cancel());
return source.token;
}
class PromiseWithTimeout {
_state;
_disposables;
promise;
_resolvePromise;
_rejectPromise;
get didTimeout() {
return this._state === 'timedout';
}
constructor(timeoutCancellationToken) {
this._state = 'pending';
this._disposables = new DisposableStore();
this.promise = new Promise((resolve, reject) => {
this._resolvePromise = resolve;
this._rejectPromise = reject;
});
if (timeoutCancellationToken.isCancellationRequested) {
this._timeout();
} else {
this._disposables.add(timeoutCancellationToken.onCancellationRequested(() => this._timeout()));
}
}
registerDisposable(disposable) {
if (this._state === 'pending') {
this._disposables.add(disposable);
} else {
disposable.dispose();
}
}
_timeout() {
if (this._state !== 'pending') {
return;
}
this._disposables.dispose();
this._state = 'timedout';
this._rejectPromise(this._createTimeoutError());
}
_createTimeoutError() {
const err = new Error('Time limit reached');
err.code = 'ETIMEDOUT';
err.syscall = 'connect';
return err;
}
resolve(value) {
if (this._state !== 'pending') {
return;
}
this._disposables.dispose();
this._state = 'resolved';
this._resolvePromise(value);
}
reject(err) {
if (this._state !== 'pending') {
return;
}
this._disposables.dispose();
this._state = 'rejected';
this._rejectPromise(err);
}
}
function readOneControlMessage(protocol, timeoutCancellationToken) {
const result = new PromiseWithTimeout(timeoutCancellationToken);
result.registerDisposable(
protocol.onControlMessage((raw) => {
const msg = JSON.parse(raw.toString());
const error = getErrorFromMessage(msg);
if (error) {
result.reject(error);
} else {
result.resolve(msg);
}
}),
);
return result.promise;
}
function createSocket(logService, socketFactory, host, port, path, query, debugLabel, timeoutCancellationToken) {
const result = new PromiseWithTimeout(timeoutCancellationToken);
socketFactory.connect(host, port, path, query, debugLabel, (err, socket) => {
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(promise, timeoutCancellationToken) {
const result = new PromiseWithTimeout(timeoutCancellationToken);
promise.then(
(res) => {
if (!result.didTimeout) {
result.resolve(res);
}
},
(err) => {
if (!result.didTimeout) {
result.reject(err);
}
},
);
return result.promise;
}
async function connectToRemoteExtensionHostAgent(options, connectionType, args, timeoutCancellationToken) {
const logPrefix = connectLogPrefix(options, connectionType);
options.logService.trace(`${logPrefix} 1/6. invoking socketFactory.connect().`);
let socket;
try {
socket = await createSocket(
options.logService,
options.socketFactory,
options.host,
options.port,
getRemoteServerRootPath(options),
`reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`,
`renderer-${connectionTypeToString(connectionType)}-${options.reconnectionToken}`,
timeoutCancellationToken,
);
} catch (error) {
options.logService.error(`${logPrefix} socketFactory.connect() failed or timed out. Error:`);
options.logService.error(error);
throw error;
}
options.logService.trace(`${logPrefix} 2/6. socketFactory.connect() was successful.`);
let protocol;
let ownsProtocol;
if (options.reconnectionProtocol) {
options.reconnectionProtocol.beginAcceptReconnection(socket, null);
protocol = options.reconnectionProtocol;
ownsProtocol = false;
} else {
protocol = new PersistentProtocol({ socket, measureRoundTripTime: true });
ownsProtocol = true;
}
options.logService.trace(`${logPrefix} 3/6. sending AuthRequest control message.`);
const message = await raceWithTimeoutCancellation(
options.signService.createNewMessage(generateUuid()),
timeoutCancellationToken,
);
const authRequest = {
type: 'auth',
auth: options.connectionToken || '00000000000000000000',
data: message.data,
};
protocol.sendControl(VSBuffer.fromString(JSON.stringify(authRequest)));
try {
const msg = await readOneControlMessage(
protocol,
combineTimeoutCancellation(timeoutCancellationToken, createTimeoutCancellation(10000)),
);
if (msg.type !== 'sign' || typeof msg.data !== 'string') {
const error = new Error('Unexpected handshake message');
error.code = 'VSCODE_CONNECTION_ERROR';
throw error;
}
options.logService.trace(`${logPrefix} 4/6. received SignRequest control message.`);
const isValid = await raceWithTimeoutCancellation(
options.signService.validate(message, msg.signedData),
timeoutCancellationToken,
);
if (!isValid) {
const error = new Error('Refused to connect to unsupported server');
error.code = 'VSCODE_CONNECTION_ERROR';
throw error;
}
const signed = await raceWithTimeoutCancellation(options.signService.sign(msg.data), timeoutCancellationToken);
const connTypeRequest = {
type: 'connectionType',
commit: options.commit,
signedData: signed,
desiredConnectionType: connectionType,
};
if (args) {
connTypeRequest.args = args;
}
options.logService.trace(`${logPrefix} 5/6. sending ConnectionTypeRequest control message.`);
protocol.sendControl(VSBuffer.fromString(JSON.stringify(connTypeRequest)));
return { protocol, ownsProtocol };
} catch (error) {
if (error && error.code === 'ETIMEDOUT') {
options.logService.error(`${logPrefix} the handshake timed out. Error:`);
options.logService.error(error);
}
if (error && error.code === 'VSCODE_CONNECTION_ERROR') {
options.logService.error(`${logPrefix} received error control message when negotiating connection. Error:`);
options.logService.error(error);
}
if (ownsProtocol) {
safeDisposeProtocolAndSocket(protocol);
}
throw error;
}
}
async function connectToRemoteExtensionHostAgentAndReadOneMessage(
options,
connectionType,
args,
timeoutCancellationToken,
) {
const startTime = Date.now();
const logPrefix = connectLogPrefix(options, connectionType);
const { protocol, ownsProtocol } = await connectToRemoteExtensionHostAgent(
options,
connectionType,
args,
timeoutCancellationToken,
);
const result = new PromiseWithTimeout(timeoutCancellationToken);
result.registerDisposable(
protocol.onControlMessage((raw) => {
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);
}
result.reject(error);
} else {
options.reconnectionProtocol?.endAcceptReconnection();
options.logService.trace(
`${logPrefix} 6/6. handshake finished, connection is up and running after ${logElapsed(startTime)}!`,
);
result.resolve({ protocol, firstMessage: msg });
}
}),
);
return result.promise;
}
async function doConnectRemoteAgentManagement(options, timeoutCancellationToken) {
const { protocol } = await connectToRemoteExtensionHostAgentAndReadOneMessage(
options,
1 /* ConnectionType.Management */,
undefined,
timeoutCancellationToken,
);
return { protocol };
}
async function doConnectRemoteAgentExtensionHost(options, startArguments, timeoutCancellationToken) {
const { protocol, firstMessage } = await connectToRemoteExtensionHostAgentAndReadOneMessage(
options,
2 /* ConnectionType.ExtensionHost */,
startArguments,
timeoutCancellationToken,
);
const debugPort = firstMessage && firstMessage.debugPort;
return { protocol, debugPort };
}
async function doConnectRemoteAgentTunnel(options, startParams, timeoutCancellationToken) {
const startTime = Date.now();
const logPrefix = connectLogPrefix(options, 3 /* ConnectionType.Tunnel */);
const { protocol } = await connectToRemoteExtensionHostAgent(
options,
3 /* ConnectionType.Tunnel */,
startParams,
timeoutCancellationToken,
);
options.logService.trace(
`${logPrefix} 6/6. handshake finished, connection is up and running after ${logElapsed(startTime)}!`,
);
return protocol;
}
async function resolveConnectionOptions(options, reconnectionToken, reconnectionProtocol) {
const { host, port, connectionToken } = await options.addressProvider.getAddress();
return {
commit: options.commit,
quality: options.quality,
host: host,
port: port,
connectionToken: connectionToken,
reconnectionToken: reconnectionToken,
reconnectionProtocol: reconnectionProtocol,
socketFactory: options.socketFactory,
signService: options.signService,
logService: options.logService,
};
}
export async function connectRemoteAgentManagement(options, remoteAuthority, clientId) {
return createInitialConnection(options, async (simpleOptions) => {
const { protocol } = await doConnectRemoteAgentManagement(simpleOptions, CancellationToken.None);
return new ManagementPersistentConnection(
options,
remoteAuthority,
clientId,
simpleOptions.reconnectionToken,
protocol,
);
});
}
export async function connectRemoteAgentExtensionHost(options, startArguments) {
return createInitialConnection(options, async (simpleOptions) => {
const { protocol, debugPort } = await doConnectRemoteAgentExtensionHost(
simpleOptions,
startArguments,
CancellationToken.None,
);
return new ExtensionHostPersistentConnection(
options,
startArguments,
simpleOptions.reconnectionToken,
protocol,
debugPort,
);
});
}
/**
* Will attempt to connect 5 times. If it fails 5 consecutive times, it will give up.
*/
async function createInitialConnection(options, connectionFactory) {
const MAX_ATTEMPTS = 5;
for (let attempt = 1; ; attempt++) {
try {
const reconnectionToken = generateUuid();
const simpleOptions = await resolveConnectionOptions(options, reconnectionToken, null);
const result = await connectionFactory(simpleOptions);
return result;
} catch (err) {
if (attempt < MAX_ATTEMPTS) {
options.logService.error(
`[remote-connection][attempt ${attempt}] An error occurred in initial connection! Will retry... Error:`,
);
options.logService.error(err);
} else {
options.logService.error(
`[remote-connection][attempt ${attempt}] An error occurred in initial connection! It will be treated as a permanent error. Error:`,
);
options.logService.error(err);
PersistentConnection.triggerPermanentFailure(0, 0, RemoteAuthorityResolverError.isHandled(err));
throw err;
}
}
}
}
export async function connectRemoteAgentTunnel(options, tunnelRemoteHost, tunnelRemotePort) {
const simpleOptions = await resolveConnectionOptions(options, generateUuid(), null);
const protocol = await doConnectRemoteAgentTunnel(
simpleOptions,
{ host: tunnelRemoteHost, port: tunnelRemotePort },
CancellationToken.None,
);
return protocol;
}
function sleep(seconds) {
return createCancelablePromise((token) => {
return new Promise((resolve, reject) => {
const timeout = setTimeout(resolve, seconds * 1000);
token.onCancellationRequested(() => {
clearTimeout(timeout);
resolve();
});
});
});
}
export class ConnectionLostEvent {
reconnectionToken;
millisSinceLastIncomingData;
type = 0 /* PersistentConnectionEventType.ConnectionLost */;
constructor(reconnectionToken, millisSinceLastIncomingData) {
this.reconnectionToken = reconnectionToken;
this.millisSinceLastIncomingData = millisSinceLastIncomingData;
}
}
export class ReconnectionWaitEvent {
reconnectionToken;
millisSinceLastIncomingData;
durationSeconds;
cancellableTimer;
type = 1 /* PersistentConnectionEventType.ReconnectionWait */;
constructor(reconnectionToken, millisSinceLastIncomingData, durationSeconds, cancellableTimer) {
this.reconnectionToken = reconnectionToken;
this.millisSinceLastIncomingData = millisSinceLastIncomingData;
this.durationSeconds = durationSeconds;
this.cancellableTimer = cancellableTimer;
}
skipWait() {
this.cancellableTimer.cancel();
}
}
export class ReconnectionRunningEvent {
reconnectionToken;
millisSinceLastIncomingData;
attempt;
type = 2 /* PersistentConnectionEventType.ReconnectionRunning */;
constructor(reconnectionToken, millisSinceLastIncomingData, attempt) {
this.reconnectionToken = reconnectionToken;
this.millisSinceLastIncomingData = millisSinceLastIncomingData;
this.attempt = attempt;
}
}
export class ConnectionGainEvent {
reconnectionToken;
millisSinceLastIncomingData;
attempt;
type = 4 /* PersistentConnectionEventType.ConnectionGain */;
constructor(reconnectionToken, millisSinceLastIncomingData, attempt) {
this.reconnectionToken = reconnectionToken;
this.millisSinceLastIncomingData = millisSinceLastIncomingData;
this.attempt = attempt;
}
}
export class ConnectionHealthChangedEvent {
reconnectionToken;
connectionHealth;
type = 5 /* PersistentConnectionEventType.ConnectionHealthChanged */;
constructor(reconnectionToken, connectionHealth) {
this.reconnectionToken = reconnectionToken;
this.connectionHealth = connectionHealth;
}
}
export class ReconnectionPermanentFailureEvent {
reconnectionToken;
millisSinceLastIncomingData;
attempt;
handled;
type = 3 /* PersistentConnectionEventType.ReconnectionPermanentFailure */;
constructor(reconnectionToken, millisSinceLastIncomingData, attempt, handled) {
this.reconnectionToken = reconnectionToken;
this.millisSinceLastIncomingData = millisSinceLastIncomingData;
this.attempt = attempt;
this.handled = handled;
}
}
export class PersistentConnection extends Disposable {
_connectionType;
_options;
reconnectionToken;
protocol;
_reconnectionFailureIsFatal;
static triggerPermanentFailure(millisSinceLastIncomingData, attempt, handled) {
this._permanentFailure = true;
this._permanentFailureMillisSinceLastIncomingData = millisSinceLastIncomingData;
this._permanentFailureAttempt = attempt;
this._permanentFailureHandled = handled;
this._instances.forEach((instance) =>
instance._gotoPermanentFailure(
this._permanentFailureMillisSinceLastIncomingData,
this._permanentFailureAttempt,
this._permanentFailureHandled,
),
);
}
static debugTriggerReconnection() {
this._instances.forEach((instance) => instance._beginReconnecting());
}
static debugPauseSocketWriting() {
this._instances.forEach((instance) => instance._pauseSocketWriting());
}
static _permanentFailure = false;
static _permanentFailureMillisSinceLastIncomingData = 0;
static _permanentFailureAttempt = 0;
static _permanentFailureHandled = false;
static _instances = [];
_onDidStateChange = this._register(new Emitter());
onDidStateChange = this._onDidStateChange.event;
_permanentFailure = false;
get _isPermanentFailure() {
return this._permanentFailure || PersistentConnection._permanentFailure;
}
_isReconnecting;
constructor(_connectionType, _options, reconnectionToken, protocol, _reconnectionFailureIsFatal) {
super();
this._connectionType = _connectionType;
this._options = _options;
this.reconnectionToken = reconnectionToken;
this.protocol = protocol;
this._reconnectionFailureIsFatal = _reconnectionFailureIsFatal;
this._isReconnecting = false;
this._onDidStateChange.fire(new ConnectionGainEvent(this.reconnectionToken, 0, 0));
this._register(
protocol.onSocketClose((e) => {
const logPrefix = commonLogPrefix(this._connectionType, this.reconnectionToken, true);
if (!e) {
this._options.logService.info(`${logPrefix} received socket close event.`);
} else if (e.type === 0 /* SocketCloseEventType.NodeSocketCloseEvent */) {
this._options.logService.info(`${logPrefix} received socket close event (hadError: ${e.hadError}).`);
if (e.error) {
this._options.logService.error(e.error);
}
} else {
this._options.logService.info(
`${logPrefix} received socket close event (wasClean: ${e.wasClean}, code: ${e.code}, reason: ${e.reason}).`,
);
if (e.event) {
this._options.logService.error(e.event);
}
}
this._beginReconnecting();
}),
);
this._register(
protocol.onSocketTimeout((e) => {
const logPrefix = commonLogPrefix(this._connectionType, this.reconnectionToken, true);
this._options.logService.info(
`${logPrefix} received socket timeout event (unacknowledgedMsgCount: ${e.unacknowledgedMsgCount}, timeSinceOldestUnacknowledgedMsg: ${e.timeSinceOldestUnacknowledgedMsg}, timeSinceLastReceivedSomeData: ${e.timeSinceLastReceivedSomeData}).`,
);
this._beginReconnecting();
}),
);
this._register(
protocol.onHighRoundTripTime((e) => {
const logPrefix = _commonLogPrefix(this._connectionType, this.reconnectionToken);
this._options.logService.info(
`${logPrefix} high roundtrip time: ${e.roundTripTime}ms (${e.recentHighRoundTripCount} of ${
5 /* ProtocolConstants.LatencySampleCount */
} recent samples)`,
);
}),
);
this._register(
protocol.onDidChangeConnectionHealth((connectionHealth) => {
this._onDidStateChange.fire(new ConnectionHealthChangedEvent(this.reconnectionToken, connectionHealth));
}),
);
PersistentConnection._instances.push(this);
this._register(
toDisposable(() => {
const myIndex = PersistentConnection._instances.indexOf(this);
if (myIndex >= 0) {
PersistentConnection._instances.splice(myIndex, 1);
}
}),
);
if (this._isPermanentFailure) {
this._gotoPermanentFailure(
PersistentConnection._permanentFailureMillisSinceLastIncomingData,
PersistentConnection._permanentFailureAttempt,
PersistentConnection._permanentFailureHandled,
);
}
}
async _beginReconnecting() {
// Only have one reconnection loop active at a time.
if (this._isReconnecting) {
return;
}
try {
this._isReconnecting = true;
await this._runReconnectingLoop();
} finally {
this._isReconnecting = false;
}
}
async _runReconnectingLoop() {
if (this._isPermanentFailure) {
// 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(this.reconnectionToken, this.protocol.getMillisSinceLastIncomingData()),
);
const TIMES = [0, 5, 5, 10, 10, 10, 10, 10, 30];
let attempt = -1;
do {
attempt++;
const waitTime = attempt < TIMES.length ? TIMES[attempt] : TIMES[TIMES.length - 1];
try {
if (waitTime > 0) {
const sleepPromise = sleep(waitTime);
this._onDidStateChange.fire(
new ReconnectionWaitEvent(
this.reconnectionToken,
this.protocol.getMillisSinceLastIncomingData(),
waitTime,
sleepPromise,
),
);
this._options.logService.info(`${logPrefix} waiting for ${waitTime} seconds before reconnecting...`);
try {
await sleepPromise;
} catch {} // User canceled timer
}
if (this._isPermanentFailure) {
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.reconnectionToken,
this.protocol.getMillisSinceLastIncomingData(),
attempt + 1,
),
);
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 this._reconnect(simpleOptions, createTimeoutCancellation(RECONNECT_TIMEOUT));
this._options.logService.info(`${logPrefix} reconnected!`);
this._onDidStateChange.fire(
new ConnectionGainEvent(this.reconnectionToken, this.protocol.getMillisSinceLastIncomingData(), attempt + 1),
);
break;
} catch (err) {
if (err.code === 'VSCODE_CONNECTION_ERROR') {
this._options.logService.error(
`${logPrefix} A permanent error occurred in the reconnecting loop! Will give up now! Error:`,
);
this._options.logService.error(err);
this._onReconnectionPermanentFailure(this.protocol.getMillisSinceLastIncomingData(), attempt + 1, false);
break;
}
if (attempt > 360) {
// ReconnectionGraceTime is 3hrs, with 30s between attempts that yields a maximum of 360 attempts
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);
this._onReconnectionPermanentFailure(this.protocol.getMillisSinceLastIncomingData(), attempt + 1, false);
break;
}
if (RemoteAuthorityResolverError.isTemporarilyNotAvailable(err)) {
this._options.logService.info(
`${logPrefix} A temporarily not available error occurred 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'
) {
this._options.logService.info(
`${logPrefix} A network error occurred while trying to reconnect, will try again...`,
);
this._options.logService.trace(err);
// try again!
continue;
}
if (isCancellationError(err)) {
this._options.logService.info(
`${logPrefix} A promise cancelation error occurred while trying to reconnect, will try again...`,
);
this._options.logService.trace(err);
// try again!
continue;
}
if (err instanceof RemoteAuthorityResolverError) {
this._options.logService.error(
`${logPrefix} A RemoteAuthorityResolverError occurred while trying to reconnect. Will give up now! Error:`,
);
this._options.logService.error(err);
this._onReconnectionPermanentFailure(
this.protocol.getMillisSinceLastIncomingData(),
attempt + 1,
RemoteAuthorityResolverError.isHandled(err),
);
break;
}
this._options.logService.error(
`${logPrefix} An unknown error occurred 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);
this._onReconnectionPermanentFailure(this.protocol.getMillisSinceLastIncomingData(), attempt + 1, false);
break;
}
} while (!this._isPermanentFailure);
}
_onReconnectionPermanentFailure(millisSinceLastIncomingData, attempt, handled) {
if (this._reconnectionFailureIsFatal) {
PersistentConnection.triggerPermanentFailure(millisSinceLastIncomingData, attempt, handled);
} else {
this._gotoPermanentFailure(millisSinceLastIncomingData, attempt, handled);
}
}
_gotoPermanentFailure(millisSinceLastIncomingData, attempt, handled) {
this._onDidStateChange.fire(
new ReconnectionPermanentFailureEvent(this.reconnectionToken, millisSinceLastIncomingData, attempt, handled),
);
safeDisposeProtocolAndSocket(this.protocol);
}
_pauseSocketWriting() {
this.protocol.pauseSocketWriting();
}
}
export class ManagementPersistentConnection extends PersistentConnection {
client;
constructor(options, remoteAuthority, clientId, reconnectionToken, protocol) {
super(1 /* ConnectionType.Management */, options, reconnectionToken, protocol, /*reconnectionFailureIsFatal*/ true);
this.client = this._register(
new Client(
protocol,
{
remoteAuthority: remoteAuthority,
clientId: clientId,
},
options.ipcLogger,
),
);
}
async _reconnect(options, timeoutCancellationToken) {
await doConnectRemoteAgentManagement(options, timeoutCancellationToken);
}
}
export class ExtensionHostPersistentConnection extends PersistentConnection {
_startArguments;
debugPort;
constructor(options, startArguments, reconnectionToken, protocol, debugPort) {
super(
2 /* ConnectionType.ExtensionHost */,
options,
reconnectionToken,
protocol,
/*reconnectionFailureIsFatal*/ false,
);
this._startArguments = startArguments;
this.debugPort = debugPort;
}
async _reconnect(options, timeoutCancellationToken) {
await doConnectRemoteAgentExtensionHost(options, this._startArguments, timeoutCancellationToken);
}
}
function safeDisposeProtocolAndSocket(protocol) {
try {
protocol.acceptDisconnect();
const socket = protocol.getSocket();
protocol.dispose();
socket.dispose();
} catch (err) {
onUnexpectedError(err);
}
}
function getErrorFromMessage(msg) {
if (msg && msg.type === 'error') {
const error = new Error(`Connection error: ${msg.reason}`);
error.code = 'VSCODE_CONNECTION_ERROR';
return error;
}
return null;
}
function stringRightPad(str, len) {
while (str.length < len) {
str += ' ';
}
return str;
}
function _commonLogPrefix(connectionType, reconnectionToken) {
return `[remote-connection][${stringRightPad(connectionTypeToString(connectionType), 13)}][${reconnectionToken.substr(
0,
5,
)}…]`;
}
function commonLogPrefix(connectionType, reconnectionToken, isReconnect) {
return `${_commonLogPrefix(connectionType, reconnectionToken)}[${isReconnect ? 'reconnect' : 'initial'}]`;
}
function connectLogPrefix(options, connectionType) {
return `${commonLogPrefix(connectionType, options.reconnectionToken, !!options.reconnectionProtocol)}[${
options.host
}:${options.port}]`;
}
function logElapsed(startTime) {
return `${Date.now() - startTime} ms`;
}