Merge pull request #180263 from microsoft/connor4312/inline-remote-resolver

remote: first cut at 'inline' remote resolvers
This commit is contained in:
Connor Peet
2023-05-15 08:31:26 -07:00
committed by GitHub
45 changed files with 1264 additions and 387 deletions
@@ -66,6 +66,11 @@
"category": "Remote-TestResolver",
"command": "vscode-testresolver.currentWindow"
},
{
"title": "Connect to TestResolver in Current Window with Managed Connection",
"category": "Remote-TestResolver",
"command": "vscode-testresolver.currentWindowManaged"
},
{
"title": "Show TestResolver Log",
"category": "Remote-TestResolver",
@@ -27,7 +27,30 @@ export function activate(context: vscode.ExtensionContext) {
let connectionPaused = false;
const connectionPausedEvent = new vscode.EventEmitter<boolean>();
function doResolve(_authority: string, progress: vscode.Progress<{ message?: string; increment?: number }>): Promise<vscode.ResolvedAuthority> {
function getTunnelFeatures(): vscode.TunnelInformation['tunnelFeatures'] {
return {
elevation: true,
privacyOptions: vscode.workspace.getConfiguration('testresolver').get('supportPublicPorts') ? [
{
id: 'public',
label: 'Public',
themeIcon: 'eye'
},
{
id: 'other',
label: 'Other',
themeIcon: 'circuit-board'
},
{
id: 'private',
label: 'Private',
themeIcon: 'eye-closed'
}
] : []
};
}
function doResolve(authority: string, progress: vscode.Progress<{ message?: string; increment?: number }>): Promise<vscode.ResolverResult> {
if (connectionPaused) {
throw vscode.RemoteAuthorityResolverError.TemporarilyNotAvailable('Not available right now');
}
@@ -150,7 +173,35 @@ export function activate(context: vscode.ExtensionContext) {
}
});
});
return serverPromise.then(serverAddr => {
return serverPromise.then((serverAddr): Promise<vscode.ResolverResult> => {
if (authority.includes('managed')) {
console.log('Connecting via a managed authority');
return Promise.resolve(new vscode.ManagedResolvedAuthority(async () => {
const remoteSocket = net.createConnection({ port: serverAddr.port });
const dataEmitter = new vscode.EventEmitter<Uint8Array>();
const closeEmitter = new vscode.EventEmitter<Error | undefined>();
const endEmitter = new vscode.EventEmitter<void>();
await new Promise((res, rej) => {
remoteSocket.on('data', d => dataEmitter.fire(d))
.on('error', err => { rej(); closeEmitter.fire(err); })
.on('close', () => endEmitter.fire())
.on('end', () => endEmitter.fire())
.on('connect', res);
});
return {
onDidReceiveMessage: dataEmitter.event,
onDidClose: closeEmitter.event,
onDidEnd: endEmitter.event,
send: d => remoteSocket.write(d),
end: () => remoteSocket.end(),
};
}, connectionToken));
}
return new Promise<vscode.ResolvedAuthority>((res, _rej) => {
const proxyServer = net.createServer(proxySocket => {
outputChannel.appendLine(`Proxy connection accepted`);
@@ -228,28 +279,7 @@ export function activate(context: vscode.ExtensionContext) {
proxyServer.listen(0, '127.0.0.1', () => {
const port = (<net.AddressInfo>proxyServer.address()).port;
outputChannel.appendLine(`Going through proxy at port ${port}`);
const r: vscode.ResolverResult = new vscode.ResolvedAuthority('127.0.0.1', port, connectionToken);
r.tunnelFeatures = {
elevation: true,
privacyOptions: vscode.workspace.getConfiguration('testresolver').get('supportPublicPorts') ? [
{
id: 'public',
label: 'Public',
themeIcon: 'eye'
},
{
id: 'other',
label: 'Other',
themeIcon: 'circuit-board'
},
{
id: 'private',
label: 'Private',
themeIcon: 'eye-closed'
}
] : []
};
res(r);
res(new vscode.ResolvedAuthority('127.0.0.1', port, connectionToken));
});
context.subscriptions.push({
dispose: () => {
@@ -264,12 +294,16 @@ export function activate(context: vscode.ExtensionContext) {
async getCanonicalURI(uri: vscode.Uri): Promise<vscode.Uri> {
return vscode.Uri.file(uri.path);
},
resolve(_authority: string): Thenable<vscode.ResolvedAuthority> {
resolve(_authority: string): Thenable<vscode.ResolverResult> {
return vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: 'Open TestResolver Remote ([details](command:vscode-testresolver.showLog))',
cancellable: false
}, (progress) => doResolve(_authority, progress));
}, async (progress) => {
const rr = await doResolve(_authority, progress);
rr.tunnelFeatures = getTunnelFeatures();
return rr;
});
},
tunnelFactory,
showCandidatePort
@@ -282,6 +316,9 @@ export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.currentWindow', () => {
return vscode.commands.executeCommand('vscode.newWindow', { remoteAuthority: 'test+test', reuseWindow: true });
}));
context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.currentWindowManaged', () => {
return vscode.commands.executeCommand('vscode.newWindow', { remoteAuthority: 'test+managed', reuseWindow: true });
}));
context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.newWindowWithError', () => {
return vscode.commands.executeCommand('vscode.newWindow', { remoteAuthority: 'test+error' });
}));
+16 -12
View File
@@ -1406,6 +1406,11 @@ export class IntervalCounter {
export type ValueCallback<T = unknown> = (value: T | Promise<T>) => void;
const enum DeferredOutcome {
Resolved,
Rejected
}
/**
* Creates a promise whose resolution or rejection can be controlled imperatively.
*/
@@ -1413,19 +1418,22 @@ export class DeferredPromise<T> {
private completeCallback!: ValueCallback<T>;
private errorCallback!: (err: unknown) => void;
private rejected = false;
private resolved = false;
private outcome?: { outcome: DeferredOutcome.Rejected; value: any } | { outcome: DeferredOutcome.Resolved; value: T };
public get isRejected() {
return this.rejected;
return this.outcome?.outcome === DeferredOutcome.Rejected;
}
public get isResolved() {
return this.resolved;
return this.outcome?.outcome === DeferredOutcome.Resolved;
}
public get isSettled() {
return this.rejected || this.resolved;
return !!this.outcome;
}
public get value() {
return this.outcome?.outcome === DeferredOutcome.Resolved ? this.outcome?.value : undefined;
}
public readonly p: Promise<T>;
@@ -1440,7 +1448,7 @@ export class DeferredPromise<T> {
public complete(value: T) {
return new Promise<void>(resolve => {
this.completeCallback(value);
this.resolved = true;
this.outcome = { outcome: DeferredOutcome.Resolved, value };
resolve();
});
}
@@ -1448,17 +1456,13 @@ export class DeferredPromise<T> {
public error(err: unknown) {
return new Promise<void>(resolve => {
this.errorCallback(err);
this.rejected = true;
this.outcome = { outcome: DeferredOutcome.Rejected, value: err };
resolve();
});
}
public cancel() {
new Promise<void>(resolve => {
this.errorCallback(new CancellationError());
this.rejected = true;
resolve();
});
return this.error(new CancellationError());
}
}
+48
View File
@@ -3,11 +3,13 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Lazy } from 'vs/base/common/lazy';
import * as streams from 'vs/base/common/stream';
declare const Buffer: any;
const hasBuffer = (typeof Buffer !== 'undefined');
const indexOfTable = new Lazy(() => new Uint8Array(256));
let textEncoder: TextEncoder | null;
let textDecoder: TextDecoder | null;
@@ -169,6 +171,52 @@ export class VSBuffer {
writeUInt8(value: number, offset: number): void {
writeUInt8(this.buffer, value, offset);
}
indexOf(subarray: VSBuffer | Uint8Array) {
const needle = subarray instanceof VSBuffer ? subarray.buffer : subarray;
const needleLen = needle.byteLength;
const haystack = this.buffer;
const haystackLen = haystack.byteLength;
if (needleLen === 0) {
return 0;
}
if (needleLen === 1) {
return haystack.indexOf(needle[0]);
}
if (needleLen > haystackLen) {
return -1;
}
// find index of the subarray using boyer-moore-horspool algorithm
const table = indexOfTable.value;
table.fill(needle.length);
for (let i = 0; i < needle.length; i++) {
table[needle[i]] = needle.length - i - 1;
}
let i = needle.length - 1;
let j = i;
let result = -1;
while (i < haystackLen) {
if (haystack[i] === needle[j]) {
if (j === 0) {
result = i;
break;
}
i--;
j--;
} else {
i += Math.max(needle.length - j, table[haystack[i]]);
j = needle.length - 1;
}
}
return result;
}
}
export function readUInt16LE(source: Uint8Array, offset: number): number {
+4
View File
@@ -1171,6 +1171,10 @@ export class PauseableEmitter<T> extends Emitter<T> {
protected _eventQueue = new LinkedList<T>();
private _mergeFn?: (input: T[]) => T;
public get isPaused(): boolean {
return this._isPaused !== 0;
}
constructor(options?: EmitterOptions & { merge?: (input: T[]) => T }) {
super(options);
this._mergeFn = options?.merge;
+16
View File
@@ -413,6 +413,22 @@ suite('Buffer', () => {
}
});
test('indexOf', () => {
const haystack = VSBuffer.fromString('abcaabbccaaabbbccc');
assert.strictEqual(haystack.indexOf(VSBuffer.fromString('')), 0);
assert.strictEqual(haystack.indexOf(VSBuffer.fromString('a'.repeat(100))), -1);
assert.strictEqual(haystack.indexOf(VSBuffer.fromString('a')), 0);
assert.strictEqual(haystack.indexOf(VSBuffer.fromString('c')), 2);
assert.strictEqual(haystack.indexOf(VSBuffer.fromString('abcaa')), 0);
assert.strictEqual(haystack.indexOf(VSBuffer.fromString('caaab')), 8);
assert.strictEqual(haystack.indexOf(VSBuffer.fromString('ccc')), 15);
assert.strictEqual(haystack.indexOf(VSBuffer.fromString('cccb')), -1);
});
suite('base64', () => {
/*
Generated with:
@@ -341,7 +341,12 @@ export interface IExtension {
*/
export class ExtensionIdentifier {
public readonly value: string;
private readonly _lower: string;
/**
* Do not use directly. This is public to avoid mangling and thus
* allow compatibility between running from source and a built version.
*/
readonly _lower: string;
constructor(value: string) {
this.value = value;
@@ -9,8 +9,8 @@ import { VSBuffer } from 'vs/base/common/buffer';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { ISocket, SocketCloseEvent, SocketCloseEventType, SocketDiagnostics, SocketDiagnosticsEventType } from 'vs/base/parts/ipc/common/ipc.net';
import { IConnectCallback, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
import { RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { ISocketFactory } from 'vs/platform/remote/common/remoteSocketFactoryService';
import { RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode, RemoteConnectionType, WebSocketRemoteConnection } from 'vs/platform/remote/common/remoteAuthorityResolver';
export interface IWebSocketFactory {
create(url: string, debugLabel: string): IWebSocket;
@@ -265,23 +265,27 @@ class BrowserSocket implements ISocket {
}
export class BrowserSocketFactory implements ISocketFactory {
export class BrowserSocketFactory implements ISocketFactory<RemoteConnectionType.WebSocket> {
private readonly _webSocketFactory: IWebSocketFactory;
constructor(webSocketFactory: IWebSocketFactory | null | undefined) {
this._webSocketFactory = webSocketFactory || defaultWebSocketFactory;
}
connect(host: string, port: number, path: string, query: string, debugLabel: string, callback: IConnectCallback): void {
const webSocketSchema = (/^https:/.test(window.location.href) ? 'wss' : 'ws');
const socket = this._webSocketFactory.create(`${webSocketSchema}://${(/:/.test(host) && !/\[/.test(host)) ? `[${host}]` : host}:${port}${path}?${query}&skipWebSocketFrames=false`, debugLabel);
const errorListener = socket.onError((err) => callback(err, undefined));
socket.onOpen(() => {
errorListener.dispose();
callback(undefined, new BrowserSocket(socket, debugLabel));
supports(connectTo: WebSocketRemoteConnection): boolean {
return true;
}
connect({ host, port }: WebSocketRemoteConnection, path: string, query: string, debugLabel: string): Promise<ISocket> {
return new Promise<ISocket>((resolve, reject) => {
const webSocketSchema = (/^https:/.test(window.location.href) ? 'wss' : 'ws');
const socket = this._webSocketFactory.create(`${webSocketSchema}://${(/:/.test(host) && !/\[/.test(host)) ? `[${host}]` : host}:${port}${path}?${query}&skipWebSocketFrames=false`, debugLabel);
const errorListener = socket.onError(reject);
socket.onOpen(() => {
errorListener.dispose();
resolve(new BrowserSocket(socket, debugLabel));
});
});
}
}
@@ -3,6 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DeferredPromise } from 'vs/base/common/async';
import * as errors from 'vs/base/common/errors';
import { Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { RemoteAuthorities } from 'vs/base/common/network';
@@ -11,7 +13,7 @@ import { StopWatch } from 'vs/base/common/stopwatch';
import { URI } from 'vs/base/common/uri';
import { ILogService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
import { IRemoteAuthorityResolverService, IRemoteConnectionData, ResolvedAuthority, ResolverResult, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IRemoteAuthorityResolverService, IRemoteConnectionData, RemoteConnectionType, ResolvedAuthority, ResolvedOptions, ResolverResult, WebSocketRemoteConnection, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { getRemoteServerRootPath, parseAuthorityWithOptionalPort } from 'vs/platform/remote/common/remoteHosts';
export class RemoteAuthorityResolverService extends Disposable implements IRemoteAuthorityResolverService {
@@ -21,12 +23,14 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
private readonly _onDidChangeConnectionData = this._register(new Emitter<void>());
public readonly onDidChangeConnectionData = this._onDidChangeConnectionData.event;
private readonly _promiseCache = new Map<string, Promise<ResolverResult>>();
private readonly _resolveAuthorityRequests = new Map<string, DeferredPromise<ResolverResult>>();
private readonly _cache = new Map<string, ResolverResult>();
private readonly _connectionToken: Promise<string> | string | undefined;
private readonly _connectionTokens: Map<string, string>;
private readonly _isWorkbenchOptionsBasedResolution: boolean;
constructor(
isWorkbenchOptionsBasedResolution: boolean,
connectionToken: Promise<string> | string | undefined,
resourceUriProvider: ((uri: URI) => URI) | undefined,
@IProductService productService: IProductService,
@@ -35,6 +39,7 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
super();
this._connectionToken = connectionToken;
this._connectionTokens = new Map<string, string>();
this._isWorkbenchOptionsBasedResolution = isWorkbenchOptionsBasedResolution;
if (resourceUriProvider) {
RemoteAuthorities.setDelegate(resourceUriProvider);
}
@@ -42,15 +47,20 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
}
async resolveAuthority(authority: string): Promise<ResolverResult> {
let result = this._promiseCache.get(authority);
let result = this._resolveAuthorityRequests.get(authority);
if (!result) {
result = this._doResolveAuthority(authority);
this._promiseCache.set(authority, result);
result = new DeferredPromise<ResolverResult>();
this._resolveAuthorityRequests.set(authority, result);
if (this._isWorkbenchOptionsBasedResolution) {
this._doResolveAuthority(authority).then(v => result!.complete(v), (err) => result!.error(err));
}
}
return result;
return result.p;
}
async getCanonicalURI(uri: URI): Promise<URI> {
// todo@connor4312 make this work for web
return uri;
}
@@ -61,8 +71,7 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
const resolverResult = this._cache.get(authority)!;
const connectionToken = this._connectionTokens.get(authority) || resolverResult.authority.connectionToken;
return {
host: resolverResult.authority.host,
port: resolverResult.authority.port,
connectTo: resolverResult.authority.connectTo,
connectionToken: connectionToken
};
}
@@ -77,20 +86,42 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
this._logService.info(`Resolved connection token (${authorityPrefix}) after ${sw.elapsed()} ms`);
const defaultPort = (/^https:/.test(window.location.href) ? 443 : 80);
const { host, port } = parseAuthorityWithOptionalPort(authority, defaultPort);
const result: ResolverResult = { authority: { authority, host: host, port: port, connectionToken } };
RemoteAuthorities.set(authority, result.authority.host, result.authority.port);
const result: ResolverResult = { authority: { authority, connectTo: new WebSocketRemoteConnection(host, port), connectionToken } };
RemoteAuthorities.set(authority, host, port);
this._cache.set(authority, result);
this._onDidChangeConnectionData.fire();
return result;
}
_clearResolvedAuthority(authority: string): void {
if (this._resolveAuthorityRequests.has(authority)) {
this._resolveAuthorityRequests.get(authority)!.cancel();
this._resolveAuthorityRequests.delete(authority);
}
}
_setResolvedAuthority(resolvedAuthority: ResolvedAuthority) {
_setResolvedAuthority(resolvedAuthority: ResolvedAuthority, options?: ResolvedOptions): void {
if (this._resolveAuthorityRequests.has(resolvedAuthority.authority)) {
const request = this._resolveAuthorityRequests.get(resolvedAuthority.authority)!;
if (resolvedAuthority.connectTo.type === RemoteConnectionType.WebSocket) {
// todo@connor4312 need to implement some kind of loopback for ext host based messaging
RemoteAuthorities.set(resolvedAuthority.authority, resolvedAuthority.connectTo.host, resolvedAuthority.connectTo.port);
}
if (resolvedAuthority.connectionToken) {
RemoteAuthorities.setConnectionToken(resolvedAuthority.authority, resolvedAuthority.connectionToken);
}
request.complete({ authority: resolvedAuthority, options });
this._onDidChangeConnectionData.fire();
}
}
_setResolvedAuthorityError(authority: string, err: any): void {
if (this._resolveAuthorityRequests.has(authority)) {
const request = this._resolveAuthorityRequests.get(authority)!;
// Avoid that this error makes it to telemetry
request.error(errors.ErrorNoTelemetry.fromError(err));
}
}
_setAuthorityConnectionToken(authority: string, connectionToken: string): void {
@@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { VSBuffer, encodeBase64 } from 'vs/base/common/buffer';
export const makeRawSocketHeaders = (path: string, query: string, deubgLabel: string) => {
// https://tools.ietf.org/html/rfc6455#section-4
const buffer = new Uint8Array(16);
for (let i = 0; i < 16; i++) {
buffer[i] = Math.round(Math.random() * 256);
}
const nonce = encodeBase64(VSBuffer.wrap(buffer));
const headers = [
`GET ws://localhost${path}?${query}&skipWebSocketFrames=true HTTP/1.1`,
`Connection: Upgrade`,
`Upgrade: websocket`,
`Sec-WebSocket-Key: ${nonce}`
];
return headers.join('\r\n') + '\r\n\r\n';
};
export const socketRawEndHeaderSequence = VSBuffer.fromString('\r\n\r\n');
@@ -16,8 +16,9 @@ import { IIPCLogger } from 'vs/base/parts/ipc/common/ipc';
import { Client, ConnectionHealth, ISocket, PersistentProtocol, ProtocolConstants, SocketCloseEventType } from 'vs/base/parts/ipc/common/ipc.net';
import { ILogService } from 'vs/platform/log/common/log';
import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { RemoteAuthorityResolverError, RemoteConnection } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts';
import { IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService';
import { ISignService } from 'vs/platform/sign/common/sign';
const RECONNECT_TIMEOUT = 30 * 1000 /* 30s */;
@@ -71,27 +72,18 @@ export interface OKMessage {
export type HandshakeMessage = AuthRequest | SignRequest | ConnectionTypeRequest | ErrorMessage | OKMessage;
interface ISimpleConnectionOptions {
interface ISimpleConnectionOptions<T extends RemoteConnection = RemoteConnection> {
commit: string | undefined;
quality: string | undefined;
host: string;
port: number;
connectTo: T;
connectionToken: string | undefined;
reconnectionToken: string;
reconnectionProtocol: PersistentProtocol | null;
socketFactory: ISocketFactory;
remoteSocketFactoryService: IRemoteSocketFactoryService;
signService: ISignService;
logService: ILogService;
}
export interface IConnectCallback {
(err: any | undefined, socket: ISocket | undefined): void;
}
export interface ISocketFactory {
connect(host: string, port: number, path: string, query: string, debugLabel: string, callback: IConnectCallback): void;
}
function createTimeoutCancellation(millis: number): CancellationToken {
const source = new CancellationTokenSource();
setTimeout(() => source.cancel(), millis);
@@ -192,31 +184,29 @@ function readOneControlMessage<T>(protocol: PersistentProtocol, timeoutCancellat
return result.promise;
}
function createSocket(logService: ILogService, socketFactory: ISocketFactory, host: string, port: number, path: string, query: string, debugConnectionType: string, debugLabel: string, timeoutCancellationToken: CancellationToken): Promise<ISocket> {
function createSocket<T extends RemoteConnection>(logService: ILogService, remoteSocketFactoryService: IRemoteSocketFactoryService, connectTo: T, path: string, query: string, debugConnectionType: string, debugLabel: string, timeoutCancellationToken: CancellationToken): Promise<ISocket> {
const result = new PromiseWithTimeout<ISocket>(timeoutCancellationToken);
const sw = StopWatch.create(false);
logService.info(`Creating a socket (${debugLabel})...`);
performance.mark(`code/willCreateSocket/${debugConnectionType}`);
socketFactory.connect(host, port, path, query, debugLabel, (err: any, socket: ISocket | undefined) => {
remoteSocketFactoryService.connect(connectTo, path, query, debugLabel).then((socket) => {
if (result.didTimeout) {
performance.mark(`code/didCreateSocketError/${debugConnectionType}`);
logService.info(`Creating a socket (${debugLabel}) finished after ${sw.elapsed()} ms, but this is too late and has timed out already.`);
if (err) {
logService.error(err);
}
socket?.dispose();
} else {
if (err || !socket) {
performance.mark(`code/didCreateSocketError/${debugConnectionType}`);
logService.info(`Creating a socket (${debugLabel}) returned an error after ${sw.elapsed()} ms.`);
result.reject(err);
} else {
performance.mark(`code/didCreateSocketOK/${debugConnectionType}`);
logService.info(`Creating a socket (${debugLabel}) was successful after ${sw.elapsed()} ms.`);
result.resolve(socket);
}
performance.mark(`code/didCreateSocketOK/${debugConnectionType}`);
logService.info(`Creating a socket (${debugLabel}) was successful after ${sw.elapsed()} ms.`);
result.resolve(socket);
}
}, (err) => {
performance.mark(`code/didCreateSocketError/${debugConnectionType}`);
logService.info(`Creating a socket (${debugLabel}) returned an error after ${sw.elapsed()} ms.`);
logService.error(err);
result.reject(err);
});
return result.promise;
}
@@ -237,14 +227,14 @@ function raceWithTimeoutCancellation<T>(promise: Promise<T>, timeoutCancellation
return result.promise;
}
async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptions, connectionType: ConnectionType, args: any | undefined, timeoutCancellationToken: CancellationToken): Promise<{ protocol: PersistentProtocol; ownsProtocol: boolean }> {
async function connectToRemoteExtensionHostAgent<T extends RemoteConnection>(options: ISimpleConnectionOptions<T>, 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.logService, options.socketFactory, options.host, options.port, getRemoteServerRootPath(options), `reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`, connectionTypeToString(connectionType), `renderer-${connectionTypeToString(connectionType)}-${options.reconnectionToken}`, timeoutCancellationToken);
socket = await createSocket(options.logService, options.remoteSocketFactoryService, options.connectTo, getRemoteServerRootPath(options), `reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`, connectionTypeToString(connectionType), `renderer-${connectionTypeToString(connectionType)}-${options.reconnectionToken}`, timeoutCancellationToken);
} catch (error) {
options.logService.error(`${logPrefix} socketFactory.connect() failed or timed out. Error:`);
options.logService.error(error);
@@ -389,40 +379,38 @@ async function doConnectRemoteAgentTunnel(options: ISimpleConnectionOptions, sta
return protocol;
}
export interface IConnectionOptions {
export interface IConnectionOptions<T extends RemoteConnection = RemoteConnection> {
commit: string | undefined;
quality: string | undefined;
socketFactory: ISocketFactory;
addressProvider: IAddressProvider;
addressProvider: IAddressProvider<T>;
remoteSocketFactoryService: IRemoteSocketFactoryService;
signService: ISignService;
logService: ILogService;
ipcLogger: IIPCLogger | null;
}
async function resolveConnectionOptions(options: IConnectionOptions, reconnectionToken: string, reconnectionProtocol: PersistentProtocol | null): Promise<ISimpleConnectionOptions> {
const { host, port, connectionToken } = await options.addressProvider.getAddress();
async function resolveConnectionOptions<T extends RemoteConnection>(options: IConnectionOptions<T>, reconnectionToken: string, reconnectionProtocol: PersistentProtocol | null): Promise<ISimpleConnectionOptions<T>> {
const { connectTo, connectionToken } = await options.addressProvider.getAddress();
return {
commit: options.commit,
quality: options.quality,
host: host,
port: port,
connectTo,
connectionToken: connectionToken,
reconnectionToken: reconnectionToken,
reconnectionProtocol: reconnectionProtocol,
socketFactory: options.socketFactory,
remoteSocketFactoryService: options.remoteSocketFactoryService,
signService: options.signService,
logService: options.logService
};
}
export interface IAddress {
host: string;
port: number;
export interface IAddress<T extends RemoteConnection = RemoteConnection> {
connectTo: T;
connectionToken: string | undefined;
}
export interface IAddressProvider {
getAddress(): Promise<IAddress>;
export interface IAddressProvider<T extends RemoteConnection = RemoteConnection> {
getAddress(): Promise<IAddress<T>>;
}
export async function connectRemoteAgentManagement(options: IConnectionOptions, remoteAuthority: string, clientId: string): Promise<ManagementPersistentConnection> {
@@ -448,7 +436,7 @@ export async function connectRemoteAgentExtensionHost(options: IConnectionOption
/**
* Will attempt to connect 5 times. If it fails 5 consecutive times, it will give up.
*/
async function createInitialConnection<T extends PersistentConnection>(options: IConnectionOptions, connectionFactory: (simpleOptions: ISimpleConnectionOptions) => Promise<T>): Promise<T> {
async function createInitialConnection<T extends PersistentConnection, O extends RemoteConnection>(options: IConnectionOptions<O>, connectionFactory: (simpleOptions: ISimpleConnectionOptions<O>) => Promise<T>): Promise<T> {
const MAX_ATTEMPTS = 5;
for (let attempt = 1; ; attempt++) {
@@ -691,7 +679,7 @@ export abstract class PersistentConnection extends Disposable {
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}...`);
this._options.logService.info(`${logPrefix} connecting to ${simpleOptions.connectTo}...`);
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));
@@ -832,7 +820,7 @@ function commonLogPrefix(connectionType: ConnectionType, reconnectionToken: stri
}
function connectLogPrefix(options: ISimpleConnectionOptions, connectionType: ConnectionType): string {
return `${commonLogPrefix(connectionType, options.reconnectionToken, !!options.reconnectionProtocol)}[${options.host}:${options.port}]`;
return `${commonLogPrefix(connectionType, options.reconnectionToken, !!options.reconnectionProtocol)}[${options.connectTo}]`;
}
function logElapsed(startTime: number): string {
@@ -10,10 +10,43 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
export const IRemoteAuthorityResolverService = createDecorator<IRemoteAuthorityResolverService>('remoteAuthorityResolverService');
export const enum RemoteConnectionType {
WebSocket,
Managed
}
export class ManagedRemoteConnection {
public readonly type = RemoteConnectionType.Managed;
constructor(
public readonly id: number
) { }
public toString(): string {
return `Managed(${this.id})`;
}
}
export class WebSocketRemoteConnection {
public readonly type = RemoteConnectionType.WebSocket;
constructor(
public readonly host: string,
public readonly port: number,
) { }
public toString(): string {
return `WebSocket(${this.host}:${this.port})`;
}
}
export type RemoteConnection = WebSocketRemoteConnection | ManagedRemoteConnection;
export type RemoteConnectionOfType<T extends RemoteConnectionType> = RemoteConnection & { type: T };
export interface ResolvedAuthority {
readonly authority: string;
readonly host: string;
readonly port: number;
readonly connectTo: RemoteConnection;
readonly connectionToken: string | undefined;
}
@@ -50,8 +83,7 @@ export interface ResolverResult {
}
export interface IRemoteConnectionData {
host: string;
port: number;
connectTo: RemoteConnection;
connectionToken: string | undefined;
}
@@ -0,0 +1,60 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { ISocket } from 'vs/base/parts/ipc/common/ipc.net';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { RemoteConnectionOfType, RemoteConnectionType, RemoteConnection } from 'vs/platform/remote/common/remoteAuthorityResolver';
export const IRemoteSocketFactoryService = createDecorator<IRemoteSocketFactoryService>('remoteSocketFactoryService');
export interface IRemoteSocketFactoryService {
readonly _serviceBrand: undefined;
/**
* Register a socket factory for the given message passing type
* @param type passing type to register for
* @param factory function that returns the socket factory, or undefined if
* it can't handle the data.
*/
register<T extends RemoteConnectionType>(type: T, factory: ISocketFactory<T>): IDisposable;
connect(connectTo: RemoteConnection, path: string, query: string, debugLabel: string): Promise<ISocket>;
}
export interface ISocketFactory<T extends RemoteConnectionType> {
supports(connectTo: RemoteConnectionOfType<T>): boolean;
connect(connectTo: RemoteConnectionOfType<T>, path: string, query: string, debugLabel: string): Promise<ISocket>;
}
export class RemoteSocketFactoryService implements IRemoteSocketFactoryService {
declare readonly _serviceBrand: undefined;
private readonly factories: { [T in RemoteConnectionType]?: ISocketFactory<T>[] } = {};
public register<T extends RemoteConnectionType>(type: T, factory: ISocketFactory<T>): IDisposable {
this.factories[type] ??= [];
this.factories[type]!.push(factory);
return toDisposable(() => {
const idx = this.factories[type]?.indexOf(factory);
if (typeof idx === 'number' && idx >= 0) {
this.factories[type]?.splice(idx, 1);
}
});
}
private getSocketFactory<T extends RemoteConnectionType>(messagePassing: RemoteConnectionOfType<T>): ISocketFactory<T> | undefined {
const factories = (this.factories[messagePassing.type] || []) as ISocketFactory<T>[];
return factories.find(factory => factory.supports(messagePassing));
}
public connect(connectTo: RemoteConnection, path: string, query: string, debugLabel: string): Promise<ISocket> {
const socketFactory = this.getSocketFactory(connectTo);
if (!socketFactory) {
throw new Error(`No socket factory found for ${connectTo}`);
}
return socketFactory.connect(connectTo, path, query, debugLabel);
}
}
@@ -3,41 +3,16 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
//
import { DeferredPromise } from 'vs/base/common/async';
import * as errors from 'vs/base/common/errors';
import { Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { RemoteAuthorities } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { IProductService } from 'vs/platform/product/common/productService';
import { IRemoteAuthorityResolverService, IRemoteConnectionData, ResolvedAuthority, ResolvedOptions, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IRemoteAuthorityResolverService, IRemoteConnectionData, RemoteConnectionType, ResolvedAuthority, ResolvedOptions, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts';
class PendingPromise<I, R> {
public readonly promise: Promise<R>;
public readonly input: I;
public result: R | null;
private _resolve!: (value: R) => void;
private _reject!: (err: any) => void;
constructor(request: I) {
this.input = request;
this.promise = new Promise<R>((resolve, reject) => {
this._resolve = resolve;
this._reject = reject;
});
this.result = null;
}
resolve(result: R): void {
this.result = result;
this._resolve(this.result);
}
reject(err: any): void {
this._reject(err);
}
}
export class RemoteAuthorityResolverService extends Disposable implements IRemoteAuthorityResolverService {
declare readonly _serviceBrand: undefined;
@@ -45,16 +20,16 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
private readonly _onDidChangeConnectionData = this._register(new Emitter<void>());
public readonly onDidChangeConnectionData = this._onDidChangeConnectionData.event;
private readonly _resolveAuthorityRequests: Map<string, PendingPromise<string, ResolverResult>>;
private readonly _resolveAuthorityRequests: Map<string, DeferredPromise<ResolverResult>>;
private readonly _connectionTokens: Map<string, string>;
private readonly _canonicalURIRequests: Map<string, PendingPromise<URI, URI>>;
private readonly _canonicalURIRequests: Map<string, { input: URI; result: DeferredPromise<URI> }>;
private _canonicalURIProvider: ((uri: URI) => Promise<URI>) | null;
constructor(@IProductService productService: IProductService) {
super();
this._resolveAuthorityRequests = new Map<string, PendingPromise<string, ResolverResult>>();
this._resolveAuthorityRequests = new Map<string, DeferredPromise<ResolverResult>>();
this._connectionTokens = new Map<string, string>();
this._canonicalURIRequests = new Map<string, PendingPromise<URI, URI>>();
this._canonicalURIRequests = new Map();
this._canonicalURIProvider = null;
RemoteAuthorities.setServerRootPath(getRemoteServerRootPath(productService));
@@ -62,19 +37,22 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
resolveAuthority(authority: string): Promise<ResolverResult> {
if (!this._resolveAuthorityRequests.has(authority)) {
this._resolveAuthorityRequests.set(authority, new PendingPromise<string, ResolverResult>(authority));
this._resolveAuthorityRequests.set(authority, new DeferredPromise());
}
return this._resolveAuthorityRequests.get(authority)!.promise;
return this._resolveAuthorityRequests.get(authority)!.p;
}
async getCanonicalURI(uri: URI): Promise<URI> {
const key = uri.toString();
if (!this._canonicalURIRequests.has(key)) {
const request = new PendingPromise<URI, URI>(uri);
this._canonicalURIProvider?.(request.input).then((uri) => request.resolve(uri), (err) => request.reject(err));
this._canonicalURIRequests.set(key, request);
const existing = this._canonicalURIRequests.get(key);
if (existing) {
return existing.result.p;
}
return this._canonicalURIRequests.get(key)!.promise;
const result = new DeferredPromise<URI>();
this._canonicalURIProvider?.(uri).then((uri) => result.complete(uri), (err) => result.error(err));
this._canonicalURIRequests.set(key, { input: uri, result });
return result.p;
}
getConnectionData(authority: string): IRemoteConnectionData | null {
@@ -82,20 +60,19 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
return null;
}
const request = this._resolveAuthorityRequests.get(authority)!;
if (!request.result) {
if (!request.isResolved) {
return null;
}
const connectionToken = this._connectionTokens.get(authority);
return {
host: request.result.authority.host,
port: request.result.authority.port,
connectTo: request.value!.authority.connectTo,
connectionToken: connectionToken
};
}
_clearResolvedAuthority(authority: string): void {
if (this._resolveAuthorityRequests.has(authority)) {
this._resolveAuthorityRequests.get(authority)!.reject(errors.canceled());
this._resolveAuthorityRequests.get(authority)!.cancel();
this._resolveAuthorityRequests.delete(authority);
}
}
@@ -103,11 +80,14 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
_setResolvedAuthority(resolvedAuthority: ResolvedAuthority, options?: ResolvedOptions): void {
if (this._resolveAuthorityRequests.has(resolvedAuthority.authority)) {
const request = this._resolveAuthorityRequests.get(resolvedAuthority.authority)!;
RemoteAuthorities.set(resolvedAuthority.authority, resolvedAuthority.host, resolvedAuthority.port);
if (resolvedAuthority.connectTo.type === RemoteConnectionType.WebSocket) {
// todo@connor4312 need to implement some kind of loopback for ext host based messaging
RemoteAuthorities.set(resolvedAuthority.authority, resolvedAuthority.connectTo.host, resolvedAuthority.connectTo.port);
}
if (resolvedAuthority.connectionToken) {
RemoteAuthorities.setConnectionToken(resolvedAuthority.authority, resolvedAuthority.connectionToken);
}
request.resolve({ authority: resolvedAuthority, options });
request.complete({ authority: resolvedAuthority, options });
this._onDidChangeConnectionData.fire();
}
}
@@ -116,7 +96,7 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
if (this._resolveAuthorityRequests.has(authority)) {
const request = this._resolveAuthorityRequests.get(authority)!;
// Avoid that this error makes it to telemetry
request.reject(errors.ErrorNoTelemetry.fromError(err));
request.error(errors.ErrorNoTelemetry.fromError(err));
}
}
@@ -128,8 +108,8 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
_setCanonicalURIProvider(provider: (uri: URI) => Promise<URI>): void {
this._canonicalURIProvider = provider;
this._canonicalURIRequests.forEach((value) => {
this._canonicalURIProvider!(value.input).then((uri) => value.resolve(uri), (err) => value.reject(err));
this._canonicalURIRequests.forEach(({ result, input }) => {
this._canonicalURIProvider!(input).then((uri) => result.complete(uri), (err) => result.error(err));
});
}
}
@@ -4,43 +4,38 @@
*--------------------------------------------------------------------------------------------*/
import * as net from 'net';
import { ISocket } from 'vs/base/parts/ipc/common/ipc.net';
import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
import { IConnectCallback, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
import { makeRawSocketHeaders } from 'vs/platform/remote/common/managedSocket';
import { RemoteConnectionType, WebSocketRemoteConnection } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { ISocketFactory } from 'vs/platform/remote/common/remoteSocketFactoryService';
export const nodeSocketFactory = new class implements ISocketFactory {
connect(host: string, port: number, path: string, query: string, debugLabel: string, callback: IConnectCallback): void {
const errorListener = (err: any) => callback(err, undefined);
export const nodeSocketFactory = new class implements ISocketFactory<RemoteConnectionType.WebSocket> {
const socket = net.createConnection({ host: host, port: port }, () => {
socket.removeListener('error', errorListener);
supports(connectTo: WebSocketRemoteConnection): boolean {
return true;
}
// https://tools.ietf.org/html/rfc6455#section-4
const buffer = Buffer.alloc(16);
for (let i = 0; i < 16; i++) {
buffer[i] = Math.round(Math.random() * 256);
}
const nonce = buffer.toString('base64');
connect({ host, port }: WebSocketRemoteConnection, path: string, query: string, debugLabel: string): Promise<ISocket> {
return new Promise<ISocket>((resolve, reject) => {
const socket = net.createConnection({ host: host, port: port }, () => {
socket.removeListener('error', reject);
const headers = [
`GET ws://${/:/.test(host) ? `[${host}]` : host}:${port}${path}?${query}&skipWebSocketFrames=true HTTP/1.1`,
`Connection: Upgrade`,
`Upgrade: websocket`,
`Sec-WebSocket-Key: ${nonce}`
];
socket.write(headers.join('\r\n') + '\r\n\r\n');
socket.write(makeRawSocketHeaders(path, query, debugLabel));
const onData = (data: Buffer) => {
const strData = data.toString();
if (strData.indexOf('\r\n\r\n') >= 0) {
// headers received OK
socket.off('data', onData);
callback(undefined, new NodeSocket(socket, debugLabel));
}
};
socket.on('data', onData);
const onData = (data: Buffer) => {
const strData = data.toString();
if (strData.indexOf('\r\n\r\n') >= 0) {
// headers received OK
socket.off('data', onData);
resolve(new NodeSocket(socket, debugLabel));
}
};
socket.on('data', onData);
});
// Disable Nagle's algorithm.
socket.setNoDelay(true);
socket.once('error', reject);
});
// Disable Nagle's algorithm.
socket.setNoDelay(true);
socket.once('error', errorListener);
}
};
+8 -6
View File
@@ -7,17 +7,17 @@ import * as net from 'net';
import * as os from 'os';
import { BROWSER_RESTRICTED_PORTS, findFreePortFaster } from 'vs/base/node/ports';
import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory';
import { Barrier } from 'vs/base/common/async';
import { Disposable } from 'vs/base/common/lifecycle';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
import { connectRemoteAgentTunnel, IAddressProvider, IConnectionOptions, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
import { connectRemoteAgentTunnel, IAddressProvider, IConnectionOptions } from 'vs/platform/remote/common/remoteAgentConnection';
import { AbstractTunnelService, isAllInterfaces, ISharedTunnelsService as ISharedTunnelsService, isLocalhost, isPortPrivileged, ITunnelService, RemoteTunnel, TunnelPrivacyId } from 'vs/platform/tunnel/common/tunnel';
import { ISignService } from 'vs/platform/sign/common/sign';
import { OS } from 'vs/base/common/platform';
import { IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService';
async function createRemoteTunnel(options: IConnectionOptions, defaultTunnelHost: string, tunnelRemoteHost: string, tunnelRemotePort: number, tunnelLocalPort?: number): Promise<RemoteTunnel> {
let readyTunnel: NodeRemoteTunnel | undefined;
@@ -155,7 +155,7 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
export class BaseTunnelService extends AbstractTunnelService {
public constructor(
private readonly socketFactory: ISocketFactory,
@IRemoteSocketFactoryService private readonly remoteSocketFactoryService: IRemoteSocketFactoryService,
@ILogService logService: ILogService,
@ISignService private readonly signService: ISignService,
@IProductService private readonly productService: IProductService,
@@ -182,8 +182,8 @@ export class BaseTunnelService extends AbstractTunnelService {
const options: IConnectionOptions = {
commit: this.productService.commit,
quality: this.productService.quality,
socketFactory: this.socketFactory,
addressProvider,
remoteSocketFactoryService: this.remoteSocketFactoryService,
signService: this.signService,
logService: this.logService,
ipcLogger: null
@@ -199,12 +199,13 @@ export class BaseTunnelService extends AbstractTunnelService {
export class TunnelService extends BaseTunnelService {
public constructor(
@IRemoteSocketFactoryService remoteSocketFactoryService: IRemoteSocketFactoryService,
@ILogService logService: ILogService,
@ISignService signService: ISignService,
@IProductService productService: IProductService,
@IConfigurationService configurationService: IConfigurationService
) {
super(nodeSocketFactory, logService, signService, productService, configurationService);
super(remoteSocketFactoryService, logService, signService, productService, configurationService);
}
}
@@ -213,6 +214,7 @@ export class SharedTunnelsService extends Disposable implements ISharedTunnelsSe
private readonly _tunnelServices: Map<string, ITunnelService> = new Map();
public constructor(
@IRemoteSocketFactoryService protected readonly remoteSocketFactoryService: IRemoteSocketFactoryService,
@ILogService protected readonly logService: ILogService,
@IProductService private readonly productService: IProductService,
@ISignService private readonly signService: ISignService,
@@ -224,7 +226,7 @@ export class SharedTunnelsService extends Disposable implements ISharedTunnelsSe
async openTunnel(authority: string, addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localHost: string, localPort?: number, elevateIfNeeded?: boolean, privacy?: string, protocol?: string): Promise<RemoteTunnel | undefined> {
this.logService.trace(`ForwardedPorts: (SharedTunnelService) openTunnel request for ${remoteHost}:${remotePort} on local port ${localPort}.`);
if (!this._tunnelServices.has(authority)) {
const tunnelService = new TunnelService(this.logService, this.signService, this.productService, this.configurationService);
const tunnelService = new TunnelService(this.remoteSocketFactoryService, this.logService, this.signService, this.productService, this.configurationService);
this._register(tunnelService);
this._tunnelServices.set(authority, tunnelService);
tunnelService.onTunnelClosed(async () => {
@@ -43,6 +43,7 @@ import './mainThreadLanguageFeatures';
import './mainThreadLanguages';
import './mainThreadLogService';
import './mainThreadMessageService';
import './mainThreadManagedSockets';
import './mainThreadOutputService';
import './mainThreadProgress';
import './mainThreadQuickDiff';
@@ -16,7 +16,7 @@ import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensio
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IRemoteConnectionData } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IRemoteConnectionData, ManagedRemoteConnection, RemoteConnection, RemoteConnectionType, ResolvedAuthority, WebSocketRemoteConnection } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { ExtHostContext, ExtHostExtensionServiceShape, MainContext, MainThreadExtensionServiceShape } from 'vs/workbench/api/common/extHost.protocol';
import { IExtension, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
@@ -26,6 +26,7 @@ import { IExtensionDescriptionDelta } from 'vs/workbench/services/extensions/com
import { IExtensionHostProxy, IResolveAuthorityResult } from 'vs/workbench/services/extensions/common/extensionHostProxy';
import { ActivationKind, ExtensionActivationReason, IExtensionService, IInternalExtensionService, MissingExtensionDependency } from 'vs/workbench/services/extensions/common/extensions';
import { extHostNamedCustomer, IExtHostContext, IInternalExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { ITimerService } from 'vs/workbench/services/timer/browser/timerService';
@@ -199,8 +200,9 @@ class ExtensionHostProxy implements IExtensionHostProxy {
private readonly _actual: ExtHostExtensionServiceShape
) { }
resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise<IResolveAuthorityResult> {
return this._actual.$resolveAuthority(remoteAuthority, resolveAttempt);
async resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise<IResolveAuthorityResult> {
const resolved = reviveResolveAuthorityResult(await this._actual.$resolveAuthority(remoteAuthority, resolveAttempt));
return resolved;
}
async getCanonicalURI(remoteAuthority: string, uri: URI): Promise<URI | null> {
const uriComponents = await this._actual.$getCanonicalURI(remoteAuthority, uri);
@@ -237,3 +239,31 @@ class ExtensionHostProxy implements IExtensionHostProxy {
return this._actual.$test_down(size);
}
}
function reviveResolveAuthorityResult(result: Dto<IResolveAuthorityResult>): IResolveAuthorityResult {
if (result.type === 'ok') {
return {
type: 'ok',
value: {
...result.value,
authority: reviveResolvedAuthority(result.value.authority),
}
};
} else {
return result;
}
}
function reviveResolvedAuthority(resolvedAuthority: Dto<ResolvedAuthority>): ResolvedAuthority {
return {
...resolvedAuthority,
connectTo: reviveConnection(resolvedAuthority.connectTo),
};
}
function reviveConnection(connection: Dto<RemoteConnection>): RemoteConnection {
if (connection.type === RemoteConnectionType.WebSocket) {
return new WebSocketRemoteConnection(connection.host, connection.port);
}
return new ManagedRemoteConnection(connection.id);
}
@@ -0,0 +1,207 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { MainContext, ExtHostContext, MainThreadManagedSocketsShape, ExtHostManagedSocketsShape } from 'vs/workbench/api/common/extHost.protocol';
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { ManagedRemoteConnection, RemoteConnectionType } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { VSBuffer } from 'vs/base/common/buffer';
import { IRemoteSocketFactoryService, ISocketFactory } from 'vs/platform/remote/common/remoteSocketFactoryService';
import { ISocket, SocketCloseEvent, SocketCloseEventType, SocketDiagnostics, SocketDiagnosticsEventType } from 'vs/base/parts/ipc/common/ipc.net';
import { Emitter, Event, PauseableEmitter } from 'vs/base/common/event';
import { makeRawSocketHeaders, socketRawEndHeaderSequence } from 'vs/platform/remote/common/managedSocket';
@extHostNamedCustomer(MainContext.MainThreadManagedSockets)
export class MainThreadManagedSockets extends Disposable implements MainThreadManagedSocketsShape {
private readonly _proxy: ExtHostManagedSocketsShape;
private readonly _registrations = new Map<number, IDisposable>();
private readonly _remoteSockets = new Map<number, RemoteSocketHalf>();
constructor(
extHostContext: IExtHostContext,
@IRemoteSocketFactoryService private readonly _remoteSocketFactoryService: IRemoteSocketFactoryService,
) {
super();
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostManagedSockets);
}
async $registerSocketFactory(socketFactoryId: number): Promise<void> {
const that = this;
const socketFactory = new class implements ISocketFactory<RemoteConnectionType.Managed> {
supports(connectTo: ManagedRemoteConnection): boolean {
return (connectTo.id === socketFactoryId);
}
connect(connectTo: ManagedRemoteConnection, path: string, query: string, debugLabel: string): Promise<ISocket> {
return new Promise<ISocket>((resolve, reject) => {
if (connectTo.id !== socketFactoryId) {
return reject(new Error('Invalid connectTo'));
}
const factoryId = connectTo.id;
that._proxy.$openRemoteSocket(factoryId).then(socketId => {
const half: RemoteSocketHalf = {
onClose: new Emitter(),
onData: new Emitter(),
onEnd: new Emitter(),
};
that._remoteSockets.set(socketId, half);
ManagedSocket.connect(socketId, that._proxy, path, query, debugLabel, half)
.then(
socket => {
socket.onDidDispose(() => that._remoteSockets.delete(socketId));
resolve(socket);
},
err => {
that._remoteSockets.delete(socketId);
reject(err);
});
}).catch(reject);
});
}
};
this._registrations.set(socketFactoryId, this._remoteSocketFactoryService.register(RemoteConnectionType.Managed, socketFactory));
}
async $unregisterSocketFactory(socketFactoryId: number): Promise<void> {
this._registrations.get(socketFactoryId)?.dispose();
}
$onDidManagedSocketHaveData(socketId: number, data: VSBuffer): void {
this._remoteSockets.get(socketId)?.onData.fire(data);
}
$onDidManagedSocketClose(socketId: number, error: string | undefined): void {
this._remoteSockets.get(socketId)?.onClose.fire({
type: SocketCloseEventType.NodeSocketCloseEvent,
error: error ? new Error(error) : undefined,
hadError: !!error
});
this._remoteSockets.delete(socketId);
}
$onDidManagedSocketEnd(socketId: number): void {
this._remoteSockets.get(socketId)?.onEnd.fire();
}
}
export interface RemoteSocketHalf {
onData: Emitter<VSBuffer>;
onClose: Emitter<SocketCloseEvent>;
onEnd: Emitter<void>;
}
export class ManagedSocket extends Disposable implements ISocket {
public static connect(
socketId: number,
proxy: ExtHostManagedSocketsShape,
path: string, query: string, debugLabel: string,
half: RemoteSocketHalf
): Promise<ManagedSocket> {
const socket = new ManagedSocket(socketId, proxy, debugLabel, half.onClose, half.onData, half.onEnd);
socket.write(VSBuffer.fromString(makeRawSocketHeaders(path, query, debugLabel)));
const d = new DisposableStore();
return new Promise<ManagedSocket>((resolve, reject) => {
let dataSoFar: VSBuffer | undefined;
d.add(socket.onData(d => {
if (!dataSoFar) {
dataSoFar = d;
} else {
dataSoFar = VSBuffer.concat([dataSoFar, d], dataSoFar.byteLength + d.byteLength);
}
const index = dataSoFar.indexOf(socketRawEndHeaderSequence);
if (index === -1) {
return;
}
resolve(socket);
// pause data events until the socket consumer is hooked up. We may
// immediately emit remaining data, but if not there may still be
// microtasks queued which would fire data into the abyss.
socket.pauseData();
const rest = dataSoFar.slice(index + socketRawEndHeaderSequence.byteLength);
if (rest.byteLength) {
half.onData.fire(rest);
}
}));
d.add(socket.onClose(err => reject(err ?? new Error('socket closed'))));
d.add(socket.onEnd(() => reject(new Error('socket ended'))));
}).finally(() => d.dispose());
}
private readonly pausableDataEmitter = this._register(new PauseableEmitter<VSBuffer>());
public onData: Event<VSBuffer> = (...args) => {
if (this.pausableDataEmitter.isPaused) {
queueMicrotask(() => this.pausableDataEmitter.resume());
}
return this.pausableDataEmitter.event(...args);
};
public onClose: Event<SocketCloseEvent>;
public onEnd: Event<void>;
private readonly didDisposeEmitter = this._register(new Emitter<void>());
public onDidDispose = this.didDisposeEmitter.event;
private ended = false;
private constructor(
private readonly socketId: number,
private readonly proxy: ExtHostManagedSocketsShape,
private readonly debugLabel: string,
onCloseEmitter: Emitter<SocketCloseEvent>,
onDataEmitter: Emitter<VSBuffer>,
onEndEmitter: Emitter<void>,
) {
super();
this._register(onDataEmitter);
this._register(onDataEmitter.event(data => this.pausableDataEmitter.fire(data)));
this.onClose = this._register(onCloseEmitter).event;
this.onEnd = this._register(onEndEmitter).event;
}
/** Pauses data events until a new listener comes in onData() */
pauseData() {
this.pausableDataEmitter.pause();
}
write(buffer: VSBuffer): void {
this.proxy.$remoteSocketWrite(this.socketId, buffer);
}
end(): void {
this.ended = true;
this.proxy.$remoteSocketEnd(this.socketId);
}
drain(): Promise<void> {
return this.proxy.$remoteSocketDrain(this.socketId);
}
traceSocketEvent(type: SocketDiagnosticsEventType, data?: any): void {
SocketDiagnostics.traceSocketEvent(this, this.debugLabel, type, data);
}
override dispose(): void {
if (!this.ended) {
this.proxy.$remoteSocketEnd(this.socketId);
}
this.didDisposeEmitter.fire();
super.dispose();
}
}
@@ -102,6 +102,7 @@ import { ExtHostInteractiveEditor } from 'vs/workbench/api/common/extHostInterac
import { ExtHostNotebookDocumentSaveParticipant } from 'vs/workbench/api/common/extHostNotebookDocumentSaveParticipant';
import { ExtHostSemanticSimilarity } from 'vs/workbench/api/common/extHostSemanticSimilarity';
import { ExtHostIssueReporter } from 'vs/workbench/api/common/extHostIssueReporter';
import { IExtHostManagedSockets } from 'vs/workbench/api/common/extHostManagedSockets';
export interface IExtensionRegistries {
mine: ExtensionDescriptionRegistry;
@@ -136,6 +137,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
const extHostWindow = accessor.get(IExtHostWindow);
const extHostSecretState = accessor.get(IExtHostSecretState);
const extHostEditorTabs = accessor.get(IExtHostEditorTabs);
const extHostManagedSockets = accessor.get(IExtHostManagedSockets);
// register addressable instances
rpcProtocol.set(ExtHostContext.ExtHostFileSystemInfo, extHostFileSystemInfo);
@@ -149,6 +151,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
rpcProtocol.set(ExtHostContext.ExtHostSecretState, extHostSecretState);
rpcProtocol.set(ExtHostContext.ExtHostTelemetry, extHostTelemetry);
rpcProtocol.set(ExtHostContext.ExtHostEditorTabs, extHostEditorTabs);
rpcProtocol.set(ExtHostContext.ExtHostManagedSockets, extHostManagedSockets);
// automatically create and register addressable instances
const extHostDecorations = rpcProtocol.set(ExtHostContext.ExtHostDecorations, accessor.get(IExtHostDecorations));
@@ -1444,6 +1447,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
InlayHintKind: extHostTypes.InlayHintKind,
RemoteAuthorityResolverError: extHostTypes.RemoteAuthorityResolverError,
ResolvedAuthority: extHostTypes.ResolvedAuthority,
ManagedResolvedAuthority: extHostTypes.ManagedResolvedAuthority,
SourceControlInputBoxValidationType: extHostTypes.SourceControlInputBoxValidationType,
ExtensionRuntime: extHostTypes.ExtensionRuntime,
TimelineItem: extHostTypes.TimelineItem,
@@ -27,6 +27,7 @@ import { ExtHostLoggerService } from 'vs/workbench/api/common/extHostLoggerServi
import { ILoggerService } from 'vs/platform/log/common/log';
import { ExtHostVariableResolverProviderService, IExtHostVariableResolverProvider } from 'vs/workbench/api/common/extHostVariableResolverService';
import { ExtHostLocalizationService, IExtHostLocalizationService } from 'vs/workbench/api/common/extHostLocalizationService';
import { ExtHostManagedSockets, IExtHostManagedSockets } from 'vs/workbench/api/common/extHostManagedSockets';
registerSingleton(IExtHostLocalizationService, ExtHostLocalizationService, InstantiationType.Delayed);
registerSingleton(ILoggerService, ExtHostLoggerService, InstantiationType.Delayed);
@@ -37,6 +38,7 @@ registerSingleton(IExtHostConsumerFileSystem, ExtHostConsumerFileSystem, Instant
registerSingleton(IExtHostDebugService, WorkerExtHostDebugService, InstantiationType.Eager);
registerSingleton(IExtHostDecorations, ExtHostDecorations, InstantiationType.Eager);
registerSingleton(IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors, InstantiationType.Eager);
registerSingleton(IExtHostManagedSockets, ExtHostManagedSockets, InstantiationType.Eager);
registerSingleton(IExtHostFileSystemInfo, ExtHostFileSystemInfo, InstantiationType.Eager);
registerSingleton(IExtHostOutputService, ExtHostOutputService, InstantiationType.Delayed);
registerSingleton(IExtHostSearch, ExtHostSearch, InstantiationType.Eager);
@@ -957,6 +957,21 @@ export interface ExtHostWebviewViewsShape {
$disposeWebviewView(webviewHandle: WebviewHandle): void;
}
export interface MainThreadManagedSocketsShape extends IDisposable {
$registerSocketFactory(socketFactoryId: number): Promise<void>;
$unregisterSocketFactory(socketFactoryId: number): Promise<void>;
$onDidManagedSocketHaveData(socketId: number, data: VSBuffer): void;
$onDidManagedSocketClose(socketId: number, error: string | undefined): void;
$onDidManagedSocketEnd(socketId: number): void;
}
export interface ExtHostManagedSocketsShape {
$openRemoteSocket(socketFactoryId: number): Promise<number>;
$remoteSocketWrite(socketId: number, buffer: VSBuffer): void;
$remoteSocketEnd(socketId: number): void;
$remoteSocketDrain(socketId: number): Promise<void>;
}
export enum CellOutputKind {
Text = 1,
Error = 2,
@@ -1590,7 +1605,7 @@ export interface ExtHostSearchShape {
}
export interface ExtHostExtensionServiceShape {
$resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise<IResolveAuthorityResult>;
$resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise<Dto<IResolveAuthorityResult>>;
/**
* Returns `null` if no resolver for `remoteAuthority` is found.
*/
@@ -2518,6 +2533,7 @@ export const MainContext = {
MainThreadInteractiveEditor: createProxyIdentifier<MainThreadInteractiveEditorShape>('MainThreadInteractiveEditor'),
MainThreadTheming: createProxyIdentifier<MainThreadThemingShape>('MainThreadTheming'),
MainThreadTunnelService: createProxyIdentifier<MainThreadTunnelServiceShape>('MainThreadTunnelService'),
MainThreadManagedSockets: createProxyIdentifier<MainThreadManagedSocketsShape>('MainThreadManagedSockets'),
MainThreadTimeline: createProxyIdentifier<MainThreadTimelineShape>('MainThreadTimeline'),
MainThreadTesting: createProxyIdentifier<MainThreadTestingShape>('MainThreadTesting'),
MainThreadLocalization: createProxyIdentifier<MainThreadLocalizationShape>('MainThreadLocalizationShape'),
@@ -2579,6 +2595,7 @@ export const ExtHostContext = {
ExtHostSemanticSimilarity: createProxyIdentifier<ExtHostSemanticSimilarityShape>('ExtHostSemanticSimilarity'),
ExtHostTheming: createProxyIdentifier<ExtHostThemingShape>('ExtHostTheming'),
ExtHostTunnelService: createProxyIdentifier<ExtHostTunnelServiceShape>('ExtHostTunnelService'),
ExtHostManagedSockets: createProxyIdentifier<ExtHostManagedSocketsShape>('ExtHostManagedSockets'),
ExtHostAuthentication: createProxyIdentifier<ExtHostAuthenticationShape>('ExtHostAuthentication'),
ExtHostTimeline: createProxyIdentifier<ExtHostTimelineShape>('ExtHostTimeline'),
ExtHostTesting: createProxyIdentifier<ExtHostTestingShape>('ExtHostTesting'),
@@ -25,8 +25,8 @@ import type * as vscode from 'vscode';
import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet, IExtensionDescription, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { VSBuffer } from 'vs/base/common/buffer';
import { ExtensionGlobalMemento, ExtensionMemento } from 'vs/workbench/api/common/extHostMemento';
import { RemoteAuthorityResolverError, ExtensionKind, ExtensionMode, ExtensionRuntime } from 'vs/workbench/api/common/extHostTypes';
import { ResolvedAuthority, ResolvedOptions, RemoteAuthorityResolverErrorCode, IRemoteConnectionData, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { RemoteAuthorityResolverError, ExtensionKind, ExtensionMode, ExtensionRuntime, ResolvedAuthority as ExtHostResolvedAuthority } from 'vs/workbench/api/common/extHostTypes';
import { ResolvedAuthority, ResolvedOptions, RemoteAuthorityResolverErrorCode, IRemoteConnectionData, getRemoteAuthorityPrefix, TunnelInformation, ManagedRemoteConnection, WebSocketRemoteConnection } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
@@ -43,6 +43,8 @@ import { IResolveAuthorityResult } from 'vs/workbench/services/extensions/common
import { IExtHostLocalizationService } from 'vs/workbench/api/common/extHostLocalizationService';
import { StopWatch } from 'vs/base/common/stopwatch';
import { setTimeout0 } from 'vs/base/common/platform';
import { IExtHostManagedSockets } from 'vs/workbench/api/common/extHostManagedSockets';
import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier';
interface ITestRunner {
/** Old test runner API, as exported from `vscode/lib/testrunner` */
@@ -130,7 +132,8 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
@IExtensionStoragePaths storagePath: IExtensionStoragePaths,
@IExtHostTunnelService extHostTunnelService: IExtHostTunnelService,
@IExtHostTerminalService extHostTerminalService: IExtHostTerminalService,
@IExtHostLocalizationService extHostLocalizationService: IExtHostLocalizationService
@IExtHostLocalizationService extHostLocalizationService: IExtHostLocalizationService,
@IExtHostManagedSockets private readonly _extHostManagedSockets: IExtHostManagedSockets,
) {
super();
this._hostUtils = hostUtils;
@@ -791,7 +794,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
return { authorityPrefix, resolver: this._resolvers[authorityPrefix] };
}
public async $resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise<IResolveAuthorityResult> {
public async $resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise<Dto<IResolveAuthorityResult>> {
const sw = StopWatch.create(false);
const prefix = () => `[resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)},${resolveAttempt})][${sw.elapsed()}ms] `;
const logInfo = (msg: string) => this._logService.info(`${prefix()}${msg}`);
@@ -822,30 +825,49 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
const result = await resolver.resolve(remoteAuthority, { resolveAttempt });
performance.mark(`code/extHost/didResolveAuthorityOK/${authorityPrefix}`);
intervalLogger.dispose();
logInfo(`returned ${result.host}:${result.port}`);
const tunnelInformation: TunnelInformation = {
environmentTunnels: result.environmentTunnels,
features: result.tunnelFeatures
};
// Split merged API result into separate authority/options
const authority: ResolvedAuthority = {
authority: remoteAuthority,
host: result.host,
port: result.port,
connectionToken: result.connectionToken
};
const options: ResolvedOptions = {
extensionHostEnv: result.extensionHostEnv,
isTrusted: result.isTrusted,
authenticationSession: result.authenticationSessionForInitializingExtensions ? { id: result.authenticationSessionForInitializingExtensions.id, providerId: result.authenticationSessionForInitializingExtensions.providerId } : undefined
};
logInfo(`returned ${result instanceof ExtHostResolvedAuthority ? `${result.host}:${result.port}` : 'managed authority'}`);
let authority: ResolvedAuthority;
if (result instanceof ExtHostResolvedAuthority) {
authority = {
authority: remoteAuthority,
connectTo: new WebSocketRemoteConnection(result.host, result.port),
connectionToken: result.connectionToken
};
} else {
// The socket factory is identified by the `resolveAttempt`, since that is a number which
// always increments and is unique over all resolve() calls in a workbench session.
const socketFactoryId = resolveAttempt;
// There is only on managed socket factory at a time, so we can just overwrite the old one.
this._extHostManagedSockets.setFactory(socketFactoryId, result.makeConnection);
authority = {
authority: remoteAuthority,
connectTo: new ManagedRemoteConnection(socketFactoryId),
connectionToken: result.connectionToken
};
}
return {
type: 'ok',
value: {
authority,
authority: authority as Dto<ResolvedAuthority>,
options,
tunnelInformation: {
environmentTunnels: result.environmentTunnels,
features: result.tunnelFeatures
}
tunnelInformation,
}
};
} catch (err) {
@@ -0,0 +1,106 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ExtHostManagedSocketsShape, MainContext, MainThreadManagedSocketsShape } from 'vs/workbench/api/common/extHost.protocol';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import * as vscode from 'vscode';
import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { VSBuffer } from 'vs/base/common/buffer';
export interface IExtHostManagedSockets extends ExtHostManagedSocketsShape {
setFactory(socketFactoryId: number, makeConnection: () => Thenable<vscode.ManagedMessagePassing>): void;
readonly _serviceBrand: undefined;
}
export const IExtHostManagedSockets = createDecorator<IExtHostManagedSockets>('IExtHostManagedSockets');
export class ExtHostManagedSockets implements IExtHostManagedSockets {
declare readonly _serviceBrand: undefined;
private readonly _proxy: MainThreadManagedSocketsShape;
private _remoteSocketIdCounter = 0;
private _factory: ManagedSocketFactory | null = null;
private readonly _managedRemoteSockets: Map<number, ManagedSocket> = new Map();
constructor(
@IExtHostRpcService extHostRpc: IExtHostRpcService,
) {
this._proxy = extHostRpc.getProxy(MainContext.MainThreadManagedSockets);
}
setFactory(socketFactoryId: number, makeConnection: () => Thenable<vscode.ManagedMessagePassing>): void {
// Terminate all previous sockets
for (const socket of this._managedRemoteSockets.values()) {
// calling dispose() will lead to it removing itself from the map
socket.dispose();
}
// Unregister previous factory
if (this._factory) {
this._proxy.$unregisterSocketFactory(this._factory.socketFactoryId);
}
this._factory = new ManagedSocketFactory(socketFactoryId, makeConnection);
this._proxy.$registerSocketFactory(this._factory.socketFactoryId);
}
async $openRemoteSocket(socketFactoryId: number): Promise<number> {
if (!this._factory || this._factory.socketFactoryId !== socketFactoryId) {
throw new Error(`No socket factory with id ${socketFactoryId}`);
}
const id = (++this._remoteSocketIdCounter);
const socket = await this._factory.makeConnection();
const disposable = new DisposableStore();
this._managedRemoteSockets.set(id, new ManagedSocket(id, socket, disposable));
disposable.add(toDisposable(() => this._managedRemoteSockets.delete(id)));
disposable.add(socket.onDidEnd(() => {
this._proxy.$onDidManagedSocketEnd(id);
disposable.dispose();
}));
disposable.add(socket.onDidClose(e => {
this._proxy.$onDidManagedSocketClose(id, e?.stack ?? e?.message);
disposable.dispose();
}));
disposable.add(socket.onDidReceiveMessage(e => this._proxy.$onDidManagedSocketHaveData(id, VSBuffer.wrap(e))));
return id;
}
$remoteSocketWrite(socketId: number, buffer: VSBuffer): void {
this._managedRemoteSockets.get(socketId)?.actual.send(buffer.buffer);
}
$remoteSocketEnd(socketId: number): void {
const socket = this._managedRemoteSockets.get(socketId);
if (socket) {
socket.actual.end();
socket.dispose();
}
}
async $remoteSocketDrain(socketId: number): Promise<void> {
await this._managedRemoteSockets.get(socketId)?.actual.drain?.();
}
}
class ManagedSocketFactory {
constructor(
public readonly socketFactoryId: number,
public readonly makeConnection: () => Thenable<vscode.ManagedMessagePassing>,
) { }
}
class ManagedSocket extends Disposable {
constructor(
public readonly socketId: number,
public readonly actual: vscode.ManagedMessagePassing,
disposer: DisposableStore,
) {
super();
this._register(disposer);
}
}
+16 -3
View File
@@ -479,6 +479,13 @@ export class Selection extends Range {
}
}
const validateConnectionToken = (connectionToken: string) => {
if (typeof connectionToken !== 'string' || connectionToken.length === 0 || !/^[0-9A-Za-z_\-]+$/.test(connectionToken)) {
throw illegalArgument('connectionToken');
}
};
export class ResolvedAuthority {
readonly host: string;
readonly port: number;
@@ -492,9 +499,7 @@ export class ResolvedAuthority {
throw illegalArgument('port');
}
if (typeof connectionToken !== 'undefined') {
if (typeof connectionToken !== 'string' || connectionToken.length === 0 || !/^[0-9A-Za-z_\-]+$/.test(connectionToken)) {
throw illegalArgument('connectionToken');
}
validateConnectionToken(connectionToken);
}
this.host = host;
this.port = Math.round(port);
@@ -502,6 +507,14 @@ export class ResolvedAuthority {
}
}
export class ManagedResolvedAuthority {
constructor(public readonly makeConnection: () => Thenable<vscode.ManagedMessagePassing>, public readonly connectionToken?: string) {
if (typeof connectionToken !== 'undefined') {
validateConnectionToken(connectionToken);
}
}
}
export class RemoteAuthorityResolverError extends Error {
static NotAvailable(message?: string, handled?: boolean): RemoteAuthorityResolverError {
@@ -0,0 +1,104 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { disposableTimeout, timeout } from 'vs/base/common/async';
import { VSBuffer } from 'vs/base/common/buffer';
import { Emitter } from 'vs/base/common/event';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { SocketCloseEvent } from 'vs/base/parts/ipc/common/ipc.net';
import { mock } from 'vs/base/test/common/mock';
import { ManagedSocket, RemoteSocketHalf } from 'vs/workbench/api/browser/mainThreadManagedSockets';
import { ExtHostManagedSocketsShape } from 'vs/workbench/api/common/extHost.protocol';
suite('MainThreadManagedSockets', () => {
suite('ManagedSocket', () => {
let extHost: ExtHostMock;
let half: RemoteSocketHalf;
class ExtHostMock extends mock<ExtHostManagedSocketsShape>() {
private onDidFire = new Emitter<void>();
public readonly events: any[] = [];
override $remoteSocketWrite(socketId: number, buffer: VSBuffer): void {
this.events.push({ socketId, data: buffer.toString() });
this.onDidFire.fire();
}
override $remoteSocketDrain(socketId: number) {
this.events.push({ socketId, event: 'drain' });
this.onDidFire.fire();
return Promise.resolve();
}
override $remoteSocketEnd(socketId: number) {
this.events.push({ socketId, event: 'end' });
this.onDidFire.fire();
}
expectEvent(test: (evt: any) => void, message: string) {
if (this.events.some(test)) {
return;
}
const d = new DisposableStore();
return new Promise<void>(resolve => {
d.add(this.onDidFire.event(() => {
if (this.events.some(test)) {
return;
}
}));
d.add(disposableTimeout(() => {
throw new Error(`Expected ${message} but only had ${JSON.stringify(this.events, null, 2)}`);
}, 1000));
}).finally(() => d.dispose());
}
}
setup(() => {
extHost = new ExtHostMock();
half = {
onClose: new Emitter<SocketCloseEvent>(),
onData: new Emitter<VSBuffer>(),
onEnd: new Emitter<void>(),
};
});
async function doConnect() {
const socket = ManagedSocket.connect(1, extHost, '/hello', 'world=true', '', half);
await extHost.expectEvent(evt => evt.data && evt.data.startsWith('GET ws://localhost/hello?world=true&skipWebSocketFrames=true HTTP/1.1\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Key:'), 'websocket open event');
half.onData.fire(VSBuffer.fromString('Opened successfully ;)\r\n\r\n'));
return await socket;
}
test('connects', async () => {
await doConnect();
});
test('includes trailing connection data', async () => {
const socketProm = ManagedSocket.connect(1, extHost, '/hello', 'world=true', '', half);
await extHost.expectEvent(evt => evt.data && evt.data.includes('GET ws://localhost'), 'websocket open event');
half.onData.fire(VSBuffer.fromString('Opened successfully ;)\r\n\r\nSome trailing data'));
const socket = await socketProm;
const data: string[] = [];
socket.onData(d => data.push(d.toString()));
await timeout(1); // allow microtasks to flush
assert.deepStrictEqual(data, ['Some trailing data']);
});
test('round trips data', async () => {
const socket = await doConnect();
const data: string[] = [];
socket.onData(d => data.push(d.toString()));
socket.write(VSBuffer.fromString('ping'));
await extHost.expectEvent(evt => evt.data === 'ping', 'expected ping');
half.onData.fire(VSBuffer.fromString("pong"));
assert.deepStrictEqual(data, ['pong']);
});
});
});
+9 -4
View File
@@ -18,7 +18,7 @@ import { IProductService } from 'vs/platform/product/common/productService';
import product from 'vs/platform/product/common/product';
import { RemoteAgentService } from 'vs/workbench/services/remote/browser/remoteAgentService';
import { RemoteAuthorityResolverService } from 'vs/platform/remote/browser/remoteAuthorityResolverService';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IRemoteAuthorityResolverService, RemoteConnectionType } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IWorkbenchFileService } from 'vs/workbench/services/files/common/files';
import { FileService } from 'vs/platform/files/common/fileService';
@@ -84,6 +84,8 @@ import { BrowserUserDataProfilesService } from 'vs/platform/userDataProfile/brow
import { timeout } from 'vs/base/common/async';
import { windowLogId } from 'vs/workbench/services/log/common/logConstants';
import { LogService } from 'vs/platform/log/common/logService';
import { IRemoteSocketFactoryService, RemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService';
import { BrowserSocketFactory } from 'vs/platform/remote/browser/browserSocketFactory';
import { VSBuffer } from 'vs/base/common/buffer';
import { IStoredWorkspace } from 'vs/platform/workspaces/common/workspaces';
import { UserDataProfileInitializer } from 'vs/workbench/services/userDataProfile/browser/userDataProfileInit';
@@ -257,7 +259,7 @@ export class BrowserMain extends Disposable {
// Remote
const connectionToken = environmentService.options.connectionToken || getCookieValue(connectionTokenCookieName);
const remoteAuthorityResolverService = new RemoteAuthorityResolverService(connectionToken, this.configuration.resourceUriProvider, productService, logService);
const remoteAuthorityResolverService = new RemoteAuthorityResolverService(!environmentService.expectsResolverExtension, connectionToken, this.configuration.resourceUriProvider, productService, logService);
serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService);
// Signing
@@ -300,7 +302,10 @@ export class BrowserMain extends Disposable {
serviceCollection.set(IUserDataProfileService, userDataProfileService);
// Remote Agent
const remoteAgentService = this._register(new RemoteAgentService(this.configuration.webSocketFactory, userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService));
const remoteSocketFactoryService = new RemoteSocketFactoryService();
remoteSocketFactoryService.register(RemoteConnectionType.WebSocket, new BrowserSocketFactory(this.configuration.webSocketFactory));
serviceCollection.set(IRemoteSocketFactoryService, remoteSocketFactoryService);
const remoteAgentService = this._register(new RemoteAgentService(remoteSocketFactoryService, userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService));
serviceCollection.set(IRemoteAgentService, remoteAgentService);
this._register(RemoteFileSystemProviderClient.register(remoteAgentService, fileService, logService));
@@ -514,7 +519,7 @@ export class BrowserMain extends Disposable {
}
}
private async createWorkspaceService(workspace: IAnyWorkspaceIdentifier, environmentService: IWorkbenchEnvironmentService, userDataProfileService: IUserDataProfileService, userDataProfilesService: IUserDataProfilesService, fileService: FileService, remoteAgentService: IRemoteAgentService, uriIdentityService: IUriIdentityService, logService: ILogService): Promise<WorkspaceService> {
private async createWorkspaceService(workspace: IAnyWorkspaceIdentifier, environmentService: IBrowserWorkbenchEnvironmentService, userDataProfileService: IUserDataProfileService, userDataProfilesService: IUserDataProfilesService, fileService: FileService, remoteAgentService: IRemoteAgentService, uriIdentityService: IUriIdentityService, logService: ILogService): Promise<WorkspaceService> {
// Temporary workspaces do not exist on startup because they are
// just in memory. As such, detect this case and eagerly create
@@ -25,7 +25,7 @@ import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services
import { IMainProcessService } from 'vs/platform/ipc/common/mainProcessService';
import { SharedProcessService } from 'vs/workbench/services/sharedProcess/electron-sandbox/sharedProcessService';
import { RemoteAuthorityResolverService } from 'vs/platform/remote/electron-sandbox/remoteAuthorityResolverService';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IRemoteAuthorityResolverService, RemoteConnectionType } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { RemoteAgentService } from 'vs/workbench/services/remote/electron-sandbox/remoteAgentService';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { FileService } from 'vs/platform/files/common/fileService';
@@ -55,6 +55,8 @@ import { PolicyChannelClient } from 'vs/platform/policy/common/policyIpc';
import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy';
import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService';
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
import { BrowserSocketFactory } from 'vs/platform/remote/browser/browserSocketFactory';
import { RemoteSocketFactoryService, IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService';
export class DesktopMain extends Disposable {
@@ -236,7 +238,10 @@ export class DesktopMain extends Disposable {
serviceCollection.set(IUserDataProfileService, userDataProfileService);
// Remote Agent
const remoteAgentService = this._register(new RemoteAgentService(userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService));
const remoteSocketFactoryService = new RemoteSocketFactoryService();
remoteSocketFactoryService.register(RemoteConnectionType.WebSocket, new BrowserSocketFactory(null));
serviceCollection.set(IRemoteSocketFactoryService, remoteSocketFactoryService);
const remoteAgentService = this._register(new RemoteAgentService(remoteSocketFactoryService, userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService));
serviceCollection.set(IRemoteAgentService, remoteAgentService);
// Remote Files
@@ -45,6 +45,7 @@ import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userData
import { updateIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge';
import { VSBuffer } from 'vs/base/common/buffer';
import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing';
import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
function getLocalUserConfigurationScopes(userDataProfile: IUserDataProfile, hasRemote: boolean): ConfigurationScope[] | undefined {
return userDataProfile.isDefault
@@ -105,7 +106,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat
constructor(
{ remoteAuthority, configurationCache }: { remoteAuthority?: string; configurationCache: IConfigurationCache },
environmentService: IWorkbenchEnvironmentService,
environmentService: IBrowserWorkbenchEnvironmentService,
private readonly userDataProfileService: IUserDataProfileService,
private readonly userDataProfilesService: IUserDataProfilesService,
private readonly fileService: IFileService,
@@ -40,12 +40,12 @@ import { VSBuffer } from 'vs/base/common/buffer';
import { RemoteAgentService } from 'vs/workbench/services/remote/browser/remoteAgentService';
import { getSingleFolderWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces';
import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { hash } from 'vs/base/common/hash';
import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService';
import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler';
import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService';
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' });
@@ -60,7 +60,7 @@ suite('ConfigurationEditing', () => {
let instantiationService: TestInstantiationService;
let userDataProfileService: IUserDataProfileService;
let environmentService: IWorkbenchEnvironmentService;
let environmentService: IBrowserWorkbenchEnvironmentService;
let fileService: IFileService;
let workspaceService: WorkspaceService;
let testObject: ConfigurationEditing;
@@ -113,7 +113,7 @@ suite('ConfigurationEditing', () => {
const uriIdentityService = new UriIdentityService(fileService);
const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService));
userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService);
const remoteAgentService = disposables.add(instantiationService.createInstance(RemoteAgentService, null));
const remoteAgentService = disposables.add(instantiationService.createInstance(RemoteAgentService));
disposables.add(fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, logService))));
instantiationService.stub(IFileService, fileService);
instantiationService.stub(IRemoteAgentService, remoteAgentService);
@@ -39,7 +39,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle';
import { Event } from 'vs/base/common/event';
import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService';
import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider';
import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
import { BrowserWorkbenchEnvironmentService, IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
import { RemoteAgentService } from 'vs/workbench/services/remote/browser/remoteAgentService';
import { RemoteAuthorityResolverService } from 'vs/platform/remote/browser/remoteAuthorityResolverService';
import { hash } from 'vs/base/common/hash';
@@ -51,6 +51,7 @@ import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler';
import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService';
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
import { TasksSchemaProperties } from 'vs/workbench/contrib/tasks/common/tasks';
import { RemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService';
function convertToWorkspacePayload(folder: URI): ISingleFolderWorkspaceIdentifier {
return {
@@ -89,7 +90,7 @@ suite('WorkspaceContextService - Folder', () => {
const uriIdentityService = new UriIdentityService(fileService);
const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService);
const userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService);
testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, new RemoteAgentService(null, userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService()));
testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, new RemoteAgentService(new RemoteSocketFactoryService(), userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(false, undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService()));
await (<WorkspaceService>testObject).initialize(convertToWorkspacePayload(folder));
});
@@ -132,7 +133,7 @@ suite('WorkspaceContextService - Folder', () => {
const uriIdentityService = new UriIdentityService(fileService);
const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService);
const userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService);
const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, new RemoteAgentService(null, userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService()));
const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, new RemoteAgentService(new RemoteSocketFactoryService(), userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(false, undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService()));
await (<WorkspaceService>testObject).initialize(convertToWorkspacePayload(folder));
const actual = testObject.getWorkspaceFolder(joinPath(folder, 'a'));
@@ -155,7 +156,7 @@ suite('WorkspaceContextService - Folder', () => {
const uriIdentityService = new UriIdentityService(fileService);
const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService);
const userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService);
const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, new RemoteAgentService(null, userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService()));
const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, new RemoteAgentService(new RemoteSocketFactoryService(), userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(false, undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService()));
await (<WorkspaceService>testObject).initialize(convertToWorkspacePayload(folder));
@@ -199,7 +200,7 @@ suite('WorkspaceContextService - Workspace', () => {
const instantiationService = <TestInstantiationService>workbenchInstantiationService(undefined, disposables);
const environmentService = TestEnvironmentService;
const remoteAgentService = disposables.add(instantiationService.createInstance(RemoteAgentService, null));
const remoteAgentService = disposables.add(instantiationService.createInstance(RemoteAgentService));
instantiationService.stub(IRemoteAgentService, remoteAgentService);
fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService())));
const uriIdentityService = new UriIdentityService(fileService);
@@ -259,7 +260,7 @@ suite('WorkspaceContextService - Workspace Editing', () => {
const instantiationService = <TestInstantiationService>workbenchInstantiationService(undefined, disposables);
const environmentService = TestEnvironmentService;
const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null);
const remoteAgentService = instantiationService.createInstance(RemoteAgentService);
instantiationService.stub(IRemoteAgentService, remoteAgentService);
fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService())));
const uriIdentityService = new UriIdentityService(fileService);
@@ -505,7 +506,7 @@ suite('WorkspaceService - Initialization', () => {
const instantiationService = <TestInstantiationService>workbenchInstantiationService(undefined, disposables);
environmentService = TestEnvironmentService;
const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null);
const remoteAgentService = instantiationService.createInstance(RemoteAgentService);
instantiationService.stub(IRemoteAgentService, remoteAgentService);
fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService())));
const uriIdentityService = new UriIdentityService(fileService);
@@ -695,7 +696,7 @@ suite('WorkspaceService - Initialization', () => {
suite('WorkspaceConfigurationService - Folder', () => {
let testObject: WorkspaceService, workspaceService: WorkspaceService, fileService: IFileService, environmentService: IWorkbenchEnvironmentService, userDataProfileService: IUserDataProfileService, instantiationService: TestInstantiationService;
let testObject: WorkspaceService, workspaceService: WorkspaceService, fileService: IFileService, environmentService: IBrowserWorkbenchEnvironmentService, userDataProfileService: IUserDataProfileService, instantiationService: TestInstantiationService;
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
const disposables: DisposableStore = new DisposableStore();
@@ -766,7 +767,7 @@ suite('WorkspaceConfigurationService - Folder', () => {
instantiationService = <TestInstantiationService>workbenchInstantiationService(undefined, disposables);
environmentService = TestEnvironmentService;
environmentService.policyFile = joinPath(folder, 'policies.json');
const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null);
const remoteAgentService = instantiationService.createInstance(RemoteAgentService);
instantiationService.stub(IRemoteAgentService, remoteAgentService);
fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService())));
const uriIdentityService = new UriIdentityService(fileService);
@@ -1519,7 +1520,7 @@ suite('WorkspaceConfigurationService - Folder', () => {
suite('WorkspaceConfigurationService - Profiles', () => {
let testObject: WorkspaceService, workspaceService: WorkspaceService, fileService: IFileService, environmentService: IWorkbenchEnvironmentService, userDataProfileService: IUserDataProfileService, instantiationService: TestInstantiationService;
let testObject: WorkspaceService, workspaceService: WorkspaceService, fileService: IFileService, environmentService: IBrowserWorkbenchEnvironmentService, userDataProfileService: IUserDataProfileService, instantiationService: TestInstantiationService;
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
const disposables: DisposableStore = new DisposableStore();
@@ -1562,7 +1563,7 @@ suite('WorkspaceConfigurationService - Profiles', () => {
instantiationService = <TestInstantiationService>workbenchInstantiationService(undefined, disposables);
environmentService = TestEnvironmentService;
environmentService.policyFile = joinPath(folder, 'policies.json');
const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null);
const remoteAgentService = instantiationService.createInstance(RemoteAgentService);
instantiationService.stub(IRemoteAgentService, remoteAgentService);
fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService())));
const uriIdentityService = new UriIdentityService(fileService);
@@ -1801,7 +1802,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => {
const instantiationService = <TestInstantiationService>workbenchInstantiationService(undefined, disposables);
environmentService = TestEnvironmentService;
const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null);
const remoteAgentService = instantiationService.createInstance(RemoteAgentService);
instantiationService.stub(IRemoteAgentService, remoteAgentService);
fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService())));
const uriIdentityService = new UriIdentityService(fileService);
@@ -32,6 +32,11 @@ export interface IBrowserWorkbenchEnvironmentService extends IWorkbenchEnvironme
* Options used to configure the workbench.
*/
readonly options?: IWorkbenchConstructionOptions;
/**
* Gets whether a resolver extension is expected for the environment.
*/
readonly expectsResolverExtension: boolean;
}
export class BrowserWorkbenchEnvironmentService implements IBrowserWorkbenchEnvironmentService {
@@ -41,6 +46,11 @@ export class BrowserWorkbenchEnvironmentService implements IBrowserWorkbenchEnvi
@memoize
get remoteAuthority(): string | undefined { return this.options.remoteAuthority; }
@memoize
get expectsResolverExtension(): boolean {
return !!this.options.remoteAuthority?.includes('+') && !this.options.webSocketFactory;
}
@memoize
get isBuilt(): boolean { return !!this.productService.commit; }
@@ -62,6 +62,9 @@ export class NativeWorkbenchEnvironmentService extends AbstractNativeEnvironment
@memoize
get remoteAuthority() { return this.configuration.remoteAuthority; }
@memoize
get expectsResolverExtension() { return !!this.configuration.remoteAuthority?.includes('+'); }
@memoize
get execPath() { return this.configuration.execPath; }
@@ -14,25 +14,28 @@ import { IAutomatedWindow, getLogs } from 'vs/platform/log/browser/log';
import { ILogService } from 'vs/platform/log/common/log';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IProductService } from 'vs/platform/product/common/productService';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
import { IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { IWebWorkerExtensionHostDataProvider, IWebWorkerExtensionHostInitData, WebWorkerExtensionHost } from 'vs/workbench/services/extensions/browser/webWorkerExtensionHost';
import { FetchFileSystemProvider } from 'vs/workbench/services/extensions/browser/webWorkerFileSystemProvider';
import { AbstractExtensionService, IExtensionHostFactory, ResolvedExtensions } from 'vs/workbench/services/extensions/common/abstractExtensionService';
import { AbstractExtensionService, IExtensionHostFactory, ResolvedExtensions, checkEnabledAndProposedAPI } from 'vs/workbench/services/extensions/common/abstractExtensionService';
import { ExtensionHostKind, ExtensionRunningPreference, IExtensionHostKindPicker, extensionHostKindToString, extensionRunningPreferenceToString } from 'vs/workbench/services/extensions/common/extensionHostKind';
import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService';
import { ExtensionRunningLocation } from 'vs/workbench/services/extensions/common/extensionRunningLocation';
import { ExtensionRunningLocationTracker } from 'vs/workbench/services/extensions/common/extensionRunningLocationTracker';
import { ExtensionRunningLocationTracker, filterExtensionDescriptions } from 'vs/workbench/services/extensions/common/extensionRunningLocationTracker';
import { ExtensionHostStartup, IExtensionHost, IExtensionService, toExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionsProposedApi } from 'vs/workbench/services/extensions/common/extensionsProposedApi';
import { dedupExtensions } from 'vs/workbench/services/extensions/common/extensionsUtil';
import { IRemoteExtensionHostDataProvider, RemoteExtensionHost } from 'vs/workbench/services/extensions/common/remoteExtensionHost';
import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { IUserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit';
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
@@ -41,7 +44,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
constructor(
@IInstantiationService instantiationService: IInstantiationService,
@INotificationService notificationService: INotificationService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IBrowserWorkbenchEnvironmentService private readonly _browserEnvironmentService: IBrowserWorkbenchEnvironmentService,
@ITelemetryService telemetryService: ITelemetryService,
@IWorkbenchExtensionEnablementService extensionEnablementService: IWorkbenchExtensionEnablementService,
@IFileService fileService: IFileService,
@@ -58,13 +61,18 @@ export class ExtensionService extends AbstractExtensionService implements IExten
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@IUserDataInitializationService private readonly _userDataInitializationService: IUserDataInitializationService,
@IUserDataProfileService private readonly _userDataProfileService: IUserDataProfileService,
@IWorkspaceTrustManagementService private readonly _workspaceTrustManagementService: IWorkspaceTrustManagementService,
@IRemoteExplorerService private readonly _remoteExplorerService: IRemoteExplorerService,
) {
const extensionsProposedApi = instantiationService.createInstance(ExtensionsProposedApi);
const extensionHostFactory = new BrowserExtensionHostFactory(
extensionsProposedApi,
() => this._scanWebExtensions(),
() => this._getExtensions(),
instantiationService,
remoteAgentService,
remoteAuthorityResolverService
remoteAuthorityResolverService,
extensionEnablementService
);
super(
extensionsProposedApi,
@@ -72,7 +80,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
new BrowserExtensionHostKindPicker(logService),
instantiationService,
notificationService,
environmentService,
_browserEnvironmentService,
telemetryService,
extensionEnablementService,
fileService,
@@ -84,7 +92,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten
logService,
remoteAgentService,
remoteExtensionsScannerService,
lifecycleService
lifecycleService,
remoteAuthorityResolverService
);
// Initialize installed extensions first and do it only after workbench is ready
@@ -129,8 +138,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
return dedupExtensions(system, user, development, this._logService);
}
protected async _resolveExtensions(): Promise<ResolvedExtensions> {
// fetch the remote environment
protected async _resolveExtensionsDefault() {
const [localExtensions, remoteExtensions] = await Promise.all([
this._scanWebExtensions(),
this._remoteExtensionsScannerService.scanExtensions()
@@ -139,6 +147,50 @@ export class ExtensionService extends AbstractExtensionService implements IExten
return new ResolvedExtensions(localExtensions, remoteExtensions, /*hasLocalProcess*/false, /*allowRemoteExtensionsInLocalWebWorker*/true);
}
protected async _resolveExtensions(): Promise<ResolvedExtensions> {
if (!this._browserEnvironmentService.expectsResolverExtension) {
return this._resolveExtensionsDefault();
}
const remoteAuthority = this._environmentService.remoteAuthority!;
// Now that the canonical URI provider has been registered, we need to wait for the trust state to be
// calculated. The trust state will be used while resolving the authority, however the resolver can
// override the trust state through the resolver result.
await this._workspaceTrustManagementService.workspaceResolved;
let resolverResult: ResolverResult;
try {
resolverResult = await this._resolveAuthorityInitial(remoteAuthority);
} catch (err) {
if (RemoteAuthorityResolverError.isHandled(err)) {
console.log(`Error handled: Not showing a notification for the error`);
}
this._remoteAuthorityResolverService._setResolvedAuthorityError(remoteAuthority, err);
// Proceed with the local extension host
return this._resolveExtensionsDefault();
}
// set the resolved authority
this._remoteAuthorityResolverService._setResolvedAuthority(resolverResult.authority, resolverResult.options);
this._remoteExplorerService.setTunnelInformation(resolverResult.tunnelInformation);
// monitor for breakage
const connection = this._remoteAgentService.getConnection();
if (connection) {
connection.onDidStateChange(async (e) => {
if (e.type === PersistentConnectionEventType.ConnectionLost) {
this._remoteAuthorityResolverService._clearResolvedAuthority(remoteAuthority);
}
});
connection.onReconnecting(() => this._resolveAuthorityAgain());
}
return this._resolveExtensionsDefault();
}
protected async _onExtensionHostExit(code: number): Promise<void> {
// Dispose everything associated with the extension host
this._doStopExtensionHosts();
@@ -149,15 +201,22 @@ export class ExtensionService extends AbstractExtensionService implements IExten
automatedWindow.codeAutomationExit(code, await getLogs(this._fileService, this._environmentService));
}
}
protected async _resolveAuthority(remoteAuthority: string): Promise<ResolverResult> {
return this._resolveAuthorityOnExtensionHosts(ExtensionHostKind.LocalWebWorker, remoteAuthority);
}
}
class BrowserExtensionHostFactory implements IExtensionHostFactory {
constructor(
private readonly _extensionsProposedApi: ExtensionsProposedApi,
private readonly _scanWebExtensions: () => Promise<IExtensionDescription[]>,
private readonly _getExtensions: () => Promise<IExtensionDescription[]>,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@IWorkbenchExtensionEnablementService private readonly _extensionEnablementService: IWorkbenchExtensionEnablementService,
) { }
createExtensionHost(runningLocations: ExtensionRunningLocationTracker, runningLocation: ExtensionRunningLocation, isInitialStart: boolean): IExtensionHost | null {
@@ -166,27 +225,39 @@ class BrowserExtensionHostFactory implements IExtensionHostFactory {
return null;
}
case ExtensionHostKind.LocalWebWorker: {
return this._instantiationService.createInstance(WebWorkerExtensionHost, runningLocation, ExtensionHostStartup.EagerAutoStart, this._createLocalExtensionHostDataProvider(runningLocations, runningLocation));
return this._instantiationService.createInstance(WebWorkerExtensionHost, runningLocation, ExtensionHostStartup.EagerAutoStart, this._createLocalExtensionHostDataProvider(runningLocations, runningLocation, isInitialStart));
}
case ExtensionHostKind.Remote: {
const remoteAgentConnection = this._remoteAgentService.getConnection();
if (remoteAgentConnection) {
return this._instantiationService.createInstance(RemoteExtensionHost, runningLocation, this._createRemoteExtensionHostDataProvider(runningLocations, remoteAgentConnection.remoteAuthority), this._remoteAgentService.socketFactory);
return this._instantiationService.createInstance(RemoteExtensionHost, runningLocation, this._createRemoteExtensionHostDataProvider(runningLocations, remoteAgentConnection.remoteAuthority));
}
return null;
}
}
}
private _createLocalExtensionHostDataProvider(runningLocations: ExtensionRunningLocationTracker, desiredRunningLocation: ExtensionRunningLocation): IWebWorkerExtensionHostDataProvider {
private _createLocalExtensionHostDataProvider(runningLocations: ExtensionRunningLocationTracker, desiredRunningLocation: ExtensionRunningLocation, isInitialStart: boolean): IWebWorkerExtensionHostDataProvider {
return {
getInitData: async (): Promise<IWebWorkerExtensionHostInitData> => {
const allExtensions = await this._getExtensions();
const localWebWorkerExtensions = runningLocations.filterByRunningLocation(allExtensions, desiredRunningLocation);
return {
allExtensions: allExtensions,
myExtensions: localWebWorkerExtensions.map(extension => extension.identifier)
};
if (isInitialStart) {
// Here we load even extensions that would be disabled by workspace trust
const localExtensions = checkEnabledAndProposedAPI(this._extensionEnablementService, this._extensionsProposedApi, await this._scanWebExtensions(), /* ignore workspace trust */true);
const runningLocation = runningLocations.computeRunningLocation(localExtensions, [], false);
const myExtensions = filterExtensionDescriptions(localExtensions, runningLocation, extRunningLocation => desiredRunningLocation.equals(extRunningLocation));
return {
allExtensions: localExtensions,
myExtensions: myExtensions.map(extension => extension.identifier)
};
} else {
// restart case
const allExtensions = await this._getExtensions();
const myExtensions = runningLocations.filterByRunningLocation(allExtensions, desiredRunningLocation);
return {
allExtensions: allExtensions,
myExtensions: myExtensions.map(extension => extension.identifier)
};
}
}
};
}
@@ -9,6 +9,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import * as perf from 'vs/base/common/performance';
import { isEqualOrParent } from 'vs/base/common/resources';
import { StopWatch } from 'vs/base/common/stopwatch';
import { URI } from 'vs/base/common/uri';
import * as nls from 'vs/nls';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -21,6 +22,7 @@ import { handleVetos } from 'vs/platform/lifecycle/common/lifecycle';
import { ILogService } from 'vs/platform/log/common/log';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IProductService } from 'vs/platform/product/common/productService';
import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode, ResolverResult, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
@@ -30,6 +32,7 @@ import { ExtensionDescriptionRegistryLock, IActivationEventsReader, LockableExte
import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions';
import { ExtensionHostKind, ExtensionRunningPreference, IExtensionHostKindPicker, extensionHostKindToString } from 'vs/workbench/services/extensions/common/extensionHostKind';
import { IExtensionHostManager, createExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager';
import { IResolveAuthorityErrorResult } from 'vs/workbench/services/extensions/common/extensionHostProxy';
import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService';
import { ExtensionRunningLocation, LocalProcessRunningLocation, LocalWebWorkerRunningLocation, RemoteRunningLocation } from 'vs/workbench/services/extensions/common/extensionRunningLocation';
import { ExtensionRunningLocationTracker, filterExtensionIdentifiers } from 'vs/workbench/services/extensions/common/extensionRunningLocationTracker';
@@ -79,6 +82,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
private _extensionHostManagers: IExtensionHostManager[] = [];
private _resolveAuthorityAttempt: number = 0;
constructor(
private readonly _extensionsProposedApi: ExtensionsProposedApi,
private readonly _extensionHostFactory: IExtensionHostFactory,
@@ -97,7 +102,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
@ILogService protected readonly _logService: ILogService,
@IRemoteAgentService protected readonly _remoteAgentService: IRemoteAgentService,
@IRemoteExtensionsScannerService protected readonly _remoteExtensionsScannerService: IRemoteExtensionsScannerService,
@ILifecycleService private readonly _lifecycleService: ILifecycleService
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
@IRemoteAuthorityResolverService protected readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
) {
super();
@@ -521,6 +527,98 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
this._onDidChangeExtensionsStatus.fire(this._registry.getAllExtensionDescriptions().map(e => e.identifier));
}
//#region remote authority resolving
protected async _resolveAuthorityInitial(remoteAuthority: string): Promise<ResolverResult> {
const MAX_ATTEMPTS = 5;
for (let attempt = 1; ; attempt++) {
try {
return this._resolveAuthorityWithLogging(remoteAuthority);
} catch (err) {
if (RemoteAuthorityResolverError.isNoResolverFound(err)) {
// There is no point in retrying if there is no resolver found
throw err;
}
if (RemoteAuthorityResolverError.isNotAvailable(err)) {
// The resolver is not available and asked us to not retry
throw err;
}
if (attempt >= MAX_ATTEMPTS) {
// Too many failed attempts, give up
throw err;
}
}
}
}
protected async _resolveAuthorityAgain(): Promise<void> {
const remoteAuthority = this._environmentService.remoteAuthority;
if (!remoteAuthority) {
return;
}
this._remoteAuthorityResolverService._clearResolvedAuthority(remoteAuthority);
try {
const result = await this._resolveAuthorityWithLogging(remoteAuthority);
this._remoteAuthorityResolverService._setResolvedAuthority(result.authority, result.options);
} catch (err) {
this._remoteAuthorityResolverService._setResolvedAuthorityError(remoteAuthority, err);
}
}
private async _resolveAuthorityWithLogging(remoteAuthority: string): Promise<ResolverResult> {
const authorityPrefix = getRemoteAuthorityPrefix(remoteAuthority);
const sw = StopWatch.create(false);
this._logService.info(`Invoking resolveAuthority(${authorityPrefix})...`);
try {
perf.mark(`code/willResolveAuthority/${authorityPrefix}`);
const result = await this._resolveAuthority(remoteAuthority);
perf.mark(`code/didResolveAuthorityOK/${authorityPrefix}`);
this._logService.info(`resolveAuthority(${authorityPrefix}) returned '${result.authority.connectTo}' after ${sw.elapsed()} ms`);
return result;
} catch (err) {
perf.mark(`code/didResolveAuthorityError/${authorityPrefix}`);
this._logService.error(`resolveAuthority(${authorityPrefix}) returned an error after ${sw.elapsed()} ms`, err);
throw err;
}
}
protected async _resolveAuthorityOnExtensionHosts(kind: ExtensionHostKind, remoteAuthority: string): Promise<ResolverResult> {
const extensionHosts = this._getExtensionHostManagers(kind);
if (extensionHosts.length === 0) {
// no local process extension hosts
throw new Error(`Cannot resolve authority`);
}
this._resolveAuthorityAttempt++;
const results = await Promise.all(extensionHosts.map(extHost => extHost.resolveAuthority(remoteAuthority, this._resolveAuthorityAttempt)));
let bestErrorResult: IResolveAuthorityErrorResult | null = null;
for (const result of results) {
if (result.type === 'ok') {
return result.value;
}
if (!bestErrorResult) {
bestErrorResult = result;
continue;
}
const bestErrorIsUnknown = (bestErrorResult.error.code === RemoteAuthorityResolverErrorCode.Unknown);
const errorIsUnknown = (result.error.code === RemoteAuthorityResolverErrorCode.Unknown);
if (bestErrorIsUnknown && !errorIsUnknown) {
bestErrorResult = result;
}
}
// we can only reach this if there is an error
throw new RemoteAuthorityResolverError(bestErrorResult!.error.message, bestErrorResult!.error.code, bestErrorResult!.error.detail);
}
//#endregion
//#region Stopping / Starting / Restarting
public stopExtensionHosts(): Promise<boolean>;
@@ -1019,6 +1117,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
protected abstract _resolveExtensions(): Promise<ResolvedExtensions>;
protected abstract _scanSingleExtension(extension: IExtension): Promise<IExtensionDescription | null>;
protected abstract _onExtensionHostExit(code: number): void;
protected abstract _resolveAuthority(remoteAuthority: string): Promise<ResolverResult>;
}
export class ResolvedExtensions {
@@ -409,7 +409,7 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager {
const resolverResult = await proxy.resolveAuthority(remoteAuthority, resolveAttempt);
intervalLogger.dispose();
if (resolverResult.type === 'ok') {
logInfo(`returned ${resolverResult.value.authority.host}:${resolverResult.value.authority.port}`);
logInfo(`returned ${resolverResult.value.authority.connectTo}`);
} else {
logError(`returned an error`, resolverResult.error);
}
@@ -16,8 +16,9 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio
import { ILabelService } from 'vs/platform/label/common/label';
import { ILogService, ILoggerService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
import { IConnectionOptions, IRemoteExtensionHostStartParams, ISocketFactory, connectRemoteAgentExtensionHost } from 'vs/platform/remote/common/remoteAgentConnection';
import { IConnectionOptions, IRemoteExtensionHostStartParams, connectRemoteAgentExtensionHost } from 'vs/platform/remote/common/remoteAgentConnection';
import { IRemoteAuthorityResolverService, IRemoteConnectionData } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService';
import { ISignService } from 'vs/platform/sign/common/sign';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { isLoggingOnly } from 'vs/platform/telemetry/common/telemetryUtils';
@@ -61,7 +62,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost {
constructor(
public readonly runningLocation: RemoteRunningLocation,
private readonly _initDataProvider: IRemoteExtensionHostDataProvider,
private readonly _socketFactory: ISocketFactory,
@IRemoteSocketFactoryService private readonly remoteSocketFactoryService: IRemoteSocketFactoryService,
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@@ -87,13 +88,13 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost {
const options: IConnectionOptions = {
commit: this._productService.commit,
quality: this._productService.quality,
socketFactory: this._socketFactory,
addressProvider: {
getAddress: async () => {
const { authority } = await this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority);
return { host: authority.host, port: authority.port, connectionToken: authority.connectionToken };
return { connectTo: authority.connectTo, connectionToken: authority.connectionToken };
}
},
remoteSocketFactoryService: this.remoteSocketFactoryService,
signService: this._signService,
logService: this._logService,
ipcLogger: null
@@ -8,7 +8,6 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { Schemas } from 'vs/base/common/network';
import * as performance from 'vs/base/common/performance';
import { isCI } from 'vs/base/common/platform';
import { StopWatch } from 'vs/base/common/stopwatch';
import { URI } from 'vs/base/common/uri';
import * as nls from 'vs/nls';
import { Categories } from 'vs/platform/action/common/actionCommonCategories';
@@ -29,7 +28,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IProductService } from 'vs/platform/product/common/productService';
import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode, ResolverResult, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IRemoteAuthorityResolverService, RemoteConnectionType, RemoteAuthorityResolverError, ResolverResult, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner';
import { getRemoteName, parseAuthorityWithPort } from 'vs/platform/remote/common/remoteHosts';
import { updateProxyConfigurationsScope } from 'vs/platform/request/common/request';
@@ -44,7 +43,6 @@ import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/commo
import { ExtensionHostKind, ExtensionRunningPreference, IExtensionHostKindPicker, extensionHostKindToString, extensionRunningPreferenceToString } from 'vs/workbench/services/extensions/common/extensionHostKind';
import { IExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager';
import { ExtensionHostExitCode } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
import { IResolveAuthorityErrorResult } from 'vs/workbench/services/extensions/common/extensionHostProxy';
import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService';
import { ExtensionRunningLocation, LocalProcessRunningLocation, LocalWebWorkerRunningLocation } from 'vs/workbench/services/extensions/common/extensionRunningLocation';
import { ExtensionRunningLocationTracker, filterExtensionDescriptions } from 'vs/workbench/services/extensions/common/extensionRunningLocationTracker';
@@ -62,7 +60,6 @@ export class NativeExtensionService extends AbstractExtensionService implements
private readonly _extensionScanner: CachedExtensionScanner;
private readonly _localCrashTracker = new ExtensionHostCrashTracker();
private _resolveAuthorityAttempt: number = 0;
constructor(
@IInstantiationService instantiationService: IInstantiationService,
@@ -80,7 +77,7 @@ export class NativeExtensionService extends AbstractExtensionService implements
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@IRemoteExtensionsScannerService remoteExtensionsScannerService: IRemoteExtensionsScannerService,
@ILifecycleService lifecycleService: ILifecycleService,
@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@INativeHostService private readonly _nativeHostService: INativeHostService,
@IHostService private readonly _hostService: IHostService,
@IRemoteExplorerService private readonly _remoteExplorerService: IRemoteExplorerService,
@@ -98,7 +95,7 @@ export class NativeExtensionService extends AbstractExtensionService implements
extensionEnablementService,
configurationService,
remoteAgentService,
_remoteAuthorityResolverService
remoteAuthorityResolverService
);
super(
extensionsProposedApi,
@@ -118,7 +115,8 @@ export class NativeExtensionService extends AbstractExtensionService implements
logService,
remoteAgentService,
remoteExtensionsScannerService,
lifecycleService
lifecycleService,
remoteAuthorityResolverService
);
this._extensionScanner = extensionScanner;
@@ -272,7 +270,7 @@ export class NativeExtensionService extends AbstractExtensionService implements
// --- impl
private async _resolveAuthority(remoteAuthority: string): Promise<ResolverResult> {
protected async _resolveAuthority(remoteAuthority: string): Promise<ResolverResult> {
const authorityPlusIndex = remoteAuthority.indexOf('+');
if (authorityPlusIndex === -1) {
@@ -281,40 +279,17 @@ export class NativeExtensionService extends AbstractExtensionService implements
return {
authority: {
authority: remoteAuthority,
host,
port,
connectTo: {
type: RemoteConnectionType.WebSocket,
host,
port
},
connectionToken: undefined
}
};
}
const localProcessExtensionHosts = this._getExtensionHostManagers(ExtensionHostKind.LocalProcess);
if (localProcessExtensionHosts.length === 0) {
// no local process extension hosts
throw new Error(`Cannot resolve authority`);
}
this._resolveAuthorityAttempt++;
const results = await Promise.all(localProcessExtensionHosts.map(extHost => extHost.resolveAuthority(remoteAuthority, this._resolveAuthorityAttempt)));
let bestErrorResult: IResolveAuthorityErrorResult | null = null;
for (const result of results) {
if (result.type === 'ok') {
return result.value;
}
if (!bestErrorResult) {
bestErrorResult = result;
continue;
}
const bestErrorIsUnknown = (bestErrorResult.error.code === RemoteAuthorityResolverErrorCode.Unknown);
const errorIsUnknown = (result.error.code === RemoteAuthorityResolverErrorCode.Unknown);
if (bestErrorIsUnknown && !errorIsUnknown) {
bestErrorResult = result;
}
}
// we can only reach this if there is an error
throw new RemoteAuthorityResolverError(bestErrorResult!.error.message, bestErrorResult!.error.code, bestErrorResult!.error.detail);
return this._resolveAuthorityOnExtensionHosts(ExtensionHostKind.LocalProcess, remoteAuthority);
}
private async _getCanonicalURI(remoteAuthority: string, uri: URI): Promise<URI> {
@@ -343,63 +318,6 @@ export class NativeExtensionService extends AbstractExtensionService implements
throw new Error(`Cannot get canonical URI because no extension is installed to resolve ${getRemoteAuthorityPrefix(remoteAuthority)}`);
}
private async _resolveAuthorityInitial(remoteAuthority: string): Promise<ResolverResult> {
const MAX_ATTEMPTS = 5;
for (let attempt = 1; ; attempt++) {
try {
return this._resolveAuthorityWithLogging(remoteAuthority);
} catch (err) {
if (RemoteAuthorityResolverError.isNoResolverFound(err)) {
// There is no point in retrying if there is no resolver found
throw err;
}
if (RemoteAuthorityResolverError.isNotAvailable(err)) {
// The resolver is not available and asked us to not retry
throw err;
}
if (attempt >= MAX_ATTEMPTS) {
// Too many failed attempts, give up
throw err;
}
}
}
}
private async _resolveAuthorityAgain(): Promise<void> {
const remoteAuthority = this._environmentService.remoteAuthority;
if (!remoteAuthority) {
return;
}
this._remoteAuthorityResolverService._clearResolvedAuthority(remoteAuthority);
try {
const result = await this._resolveAuthorityWithLogging(remoteAuthority);
this._remoteAuthorityResolverService._setResolvedAuthority(result.authority, result.options);
} catch (err) {
this._remoteAuthorityResolverService._setResolvedAuthorityError(remoteAuthority, err);
}
}
private async _resolveAuthorityWithLogging(remoteAuthority: string): Promise<ResolverResult> {
const authorityPrefix = getRemoteAuthorityPrefix(remoteAuthority);
const sw = StopWatch.create(false);
this._logService.info(`Invoking resolveAuthority(${authorityPrefix})...`);
try {
performance.mark(`code/willResolveAuthority/${authorityPrefix}`);
const result = await this._resolveAuthority(remoteAuthority);
performance.mark(`code/didResolveAuthorityOK/${authorityPrefix}`);
this._logService.info(`resolveAuthority(${authorityPrefix}) returned '${result.authority.host}:${result.authority.port}' after ${sw.elapsed()} ms`);
return result;
} catch (err) {
performance.mark(`code/didResolveAuthorityError/${authorityPrefix}`);
this._logService.error(`resolveAuthority(${authorityPrefix}) returned an error after ${sw.elapsed()} ms`, err);
throw err;
}
}
protected async _resolveExtensions(): Promise<ResolvedExtensions> {
this._extensionScanner.startScanningExtensions();
@@ -631,7 +549,7 @@ class NativeExtensionHostFactory implements IExtensionHostFactory {
case ExtensionHostKind.Remote: {
const remoteAgentConnection = this._remoteAgentService.getConnection();
if (remoteAgentConnection) {
return this._instantiationService.createInstance(RemoteExtensionHost, runningLocation, this._createRemoteExtensionHostDataProvider(runningLocations, remoteAgentConnection.remoteAuthority), this._remoteAgentService.socketFactory);
return this._instantiationService.createInstance(RemoteExtensionHost, runningLocation, this._createRemoteExtensionHostDataProvider(runningLocations, remoteAgentConnection.remoteAuthority));
}
return null;
}
@@ -19,6 +19,8 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
import product from 'vs/platform/product/common/product';
import { IProductService } from 'vs/platform/product/common/productService';
import { RemoteAuthorityResolverService } from 'vs/platform/remote/browser/remoteAuthorityResolverService';
import { IRemoteAuthorityResolverService, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
@@ -145,6 +147,7 @@ suite('ExtensionService', () => {
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@IRemoteExtensionsScannerService remoteExtensionsScannerService: IRemoteExtensionsScannerService,
@ILifecycleService lifecycleService: ILifecycleService,
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService,
) {
const extensionsProposedApi = instantiationService.createInstance(ExtensionsProposedApi);
const extensionHostFactory = new class implements IExtensionHostFactory {
@@ -172,7 +175,8 @@ suite('ExtensionService', () => {
logService,
remoteAgentService,
remoteExtensionsScannerService,
lifecycleService
lifecycleService,
remoteAuthorityResolverService
);
}
@@ -205,6 +209,9 @@ suite('ExtensionService', () => {
protected _onExtensionHostExit(code: number): void {
throw new Error('Method not implemented.');
}
protected _resolveAuthority(remoteAuthority: string): Promise<ResolverResult> {
throw new Error('Method not implemented.');
}
}
let disposables: DisposableStore;
@@ -236,6 +243,7 @@ suite('ExtensionService', () => {
[IUserDataProfileService, TestUserDataProfileService],
[IUriIdentityService, UriIdentityService],
[IRemoteExtensionsScannerService, TestRemoteExtensionsScannerService],
[IRemoteAuthorityResolverService, RemoteAuthorityResolverService]
]);
extService = <MyTestExtensionService>instantiationService.get(IExtensionService);
});
@@ -9,7 +9,6 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA
import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { AbstractRemoteAgentService } from 'vs/workbench/services/remote/common/abstractRemoteAgentService';
import { IProductService } from 'vs/platform/product/common/productService';
import { IWebSocketFactory, BrowserSocketFactory } from 'vs/platform/remote/browser/browserSocketFactory';
import { ISignService } from 'vs/platform/sign/common/sign';
import { ILogService } from 'vs/platform/log/common/log';
import { Severity } from 'vs/platform/notification/common/notification';
@@ -19,11 +18,12 @@ import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions } f
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
import { IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService';
export class RemoteAgentService extends AbstractRemoteAgentService implements IRemoteAgentService {
constructor(
webSocketFactory: IWebSocketFactory | null | undefined,
@IRemoteSocketFactoryService remoteSocketFactoryService: IRemoteSocketFactoryService,
@IUserDataProfileService userDataProfileService: IUserDataProfileService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IProductService productService: IProductService,
@@ -31,7 +31,7 @@ export class RemoteAgentService extends AbstractRemoteAgentService implements IR
@ISignService signService: ISignService,
@ILogService logService: ILogService
) {
super(new BrowserSocketFactory(webSocketFactory), userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService);
super(remoteSocketFactoryService, userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService);
}
}
@@ -7,7 +7,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { IChannel, IServerChannel, getDelayedChannel, IPCLogger } from 'vs/base/parts/ipc/common/ipc';
import { Client } from 'vs/base/parts/ipc/common/ipc.net';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { connectRemoteAgentManagement, IConnectionOptions, ISocketFactory, ManagementPersistentConnection, PersistentConnectionEvent } from 'vs/platform/remote/common/remoteAgentConnection';
import { connectRemoteAgentManagement, IConnectionOptions, ManagementPersistentConnection, PersistentConnectionEvent } from 'vs/platform/remote/common/remoteAgentConnection';
import { IExtensionHostExitInfo, IRemoteAgentConnection, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { RemoteAgentConnectionContext, IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
@@ -19,17 +19,17 @@ import { ILogService } from 'vs/platform/log/common/log';
import { ITelemetryData, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry';
import { IProductService } from 'vs/platform/product/common/productService';
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
import { IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService';
export abstract class AbstractRemoteAgentService extends Disposable implements IRemoteAgentService {
declare readonly _serviceBrand: undefined;
public readonly socketFactory: ISocketFactory;
private readonly _connection: IRemoteAgentConnection | null;
private _environment: Promise<IRemoteAgentEnvironment | null> | null;
constructor(
socketFactory: ISocketFactory,
@IRemoteSocketFactoryService private readonly remoteSocketFactoryService: IRemoteSocketFactoryService,
@IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService,
@IWorkbenchEnvironmentService protected readonly _environmentService: IWorkbenchEnvironmentService,
@IProductService productService: IProductService,
@@ -38,9 +38,8 @@ export abstract class AbstractRemoteAgentService extends Disposable implements I
@ILogService logService: ILogService
) {
super();
this.socketFactory = socketFactory;
if (this._environmentService.remoteAuthority) {
this._connection = this._register(new RemoteAgentConnection(this._environmentService.remoteAuthority, productService.commit, productService.quality, this.socketFactory, this._remoteAuthorityResolverService, signService, logService));
this._connection = this._register(new RemoteAgentConnection(this._environmentService.remoteAuthority, productService.commit, productService.quality, this.remoteSocketFactoryService, this._remoteAuthorityResolverService, signService, logService));
} else {
this._connection = null;
}
@@ -150,7 +149,7 @@ class RemoteAgentConnection extends Disposable implements IRemoteAgentConnection
remoteAuthority: string,
private readonly _commit: string | undefined,
private readonly _quality: string | undefined,
private readonly _socketFactory: ISocketFactory,
private readonly _remoteSocketFactoryService: IRemoteSocketFactoryService,
private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
private readonly _signService: ISignService,
private readonly _logService: ILogService
@@ -196,7 +195,6 @@ class RemoteAgentConnection extends Disposable implements IRemoteAgentConnection
const options: IConnectionOptions = {
commit: this._commit,
quality: this._quality,
socketFactory: this._socketFactory,
addressProvider: {
getAddress: async () => {
if (firstCall) {
@@ -205,9 +203,10 @@ class RemoteAgentConnection extends Disposable implements IRemoteAgentConnection
this._onReconnecting.fire(undefined);
}
const { authority } = await this._remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority);
return { host: authority.host, port: authority.port, connectionToken: authority.connectionToken };
return { connectTo: authority.connectTo, connectionToken: authority.connectionToken };
}
},
remoteSocketFactoryService: this._remoteSocketFactoryService,
signService: this._signService,
logService: this._logService,
ipcLogger: false ? new IPCLogger(`Local \u2192 Remote`, `Remote \u2192 Local`) : null
@@ -8,7 +8,7 @@ import { RemoteAgentConnectionContext, IRemoteAgentEnvironment } from 'vs/platfo
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics';
import { Event } from 'vs/base/common/event';
import { PersistentConnectionEvent, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
import { PersistentConnectionEvent } from 'vs/platform/remote/common/remoteAgentConnection';
import { ITelemetryData, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry';
export const IRemoteAgentService = createDecorator<IRemoteAgentService>('remoteAgentService');
@@ -16,8 +16,6 @@ export const IRemoteAgentService = createDecorator<IRemoteAgentService>('remoteA
export interface IRemoteAgentService {
readonly _serviceBrand: undefined;
readonly socketFactory: ISocketFactory;
getConnection(): IRemoteAgentConnection | null;
/**
* Get the remote environment. In case of an error, returns `null`.
@@ -5,9 +5,8 @@
import * as nls from 'vs/nls';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IRemoteAuthorityResolverService, RemoteConnectionType, RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IProductService } from 'vs/platform/product/common/productService';
import { BrowserSocketFactory } from 'vs/platform/remote/browser/browserSocketFactory';
import { AbstractRemoteAgentService } from 'vs/workbench/services/remote/common/abstractRemoteAgentService';
import { ISignService } from 'vs/platform/sign/common/sign';
import { ILogService } from 'vs/platform/log/common/log';
@@ -21,9 +20,11 @@ import { INativeHostService } from 'vs/platform/native/common/native';
import { URI } from 'vs/base/common/uri';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
import { IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService';
export class RemoteAgentService extends AbstractRemoteAgentService implements IRemoteAgentService {
constructor(
@IRemoteSocketFactoryService remoteSocketFactoryService: IRemoteSocketFactoryService,
@IUserDataProfileService userDataProfileService: IUserDataProfileService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IProductService productService: IProductService,
@@ -31,7 +32,7 @@ export class RemoteAgentService extends AbstractRemoteAgentService implements IR
@ISignService signService: ISignService,
@ILogService logService: ILogService,
) {
super(new BrowserSocketFactory(null), userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService);
super(remoteSocketFactoryService, userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService);
}
}
@@ -79,12 +80,12 @@ class RemoteConnectionFailureNotificationContribution implements IWorkbenchContr
return null;
}
const connectionData = this._remoteAuthorityResolverService.getConnectionData(remoteAgentConnection.remoteAuthority);
if (!connectionData) {
if (!connectionData || connectionData.connectTo.type !== RemoteConnectionType.WebSocket) {
return null;
}
return URI.from({
scheme: 'http',
authority: `${connectionData.host}:${connectionData.port}`,
authority: `${connectionData.connectTo.host}:${connectionData.connectTo.port}`,
path: `/version`
});
}
@@ -156,7 +156,6 @@ import { IExtensionHostExitInfo, IRemoteAgentConnection, IRemoteAgentService } f
import { ILanguageDetectionService } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService';
import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics';
import { ExtensionType, IExtension, IExtensionDescription, IRelaxedExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions';
import { ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { ILayoutOffsetInfo } from 'vs/platform/layout/browser/layoutService';
import { IUserDataProfile, IUserDataProfilesService, toUserDataProfile, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
@@ -167,6 +166,7 @@ import { InstallVSIXOptions, ILocalExtension, IGalleryExtension, InstallOptions,
import { Codicon } from 'vs/base/common/codicons';
import { IHoverOptions, IHoverService, IHoverWidget } from 'vs/workbench/services/hover/browser/hover';
import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner';
import { IRemoteSocketFactoryService, RemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService';
export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput {
return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined, undefined, undefined, undefined, undefined);
@@ -325,6 +325,7 @@ export function workbenchInstantiationService(
instantiationService.stub(IWorkspaceTrustManagementService, new TestWorkspaceTrustManagementService());
instantiationService.stub(ITerminalInstanceService, new TestTerminalInstanceService());
instantiationService.stub(IElevatedFileService, new BrowserElevatedFileService());
instantiationService.stub(IRemoteSocketFactoryService, new RemoteSocketFactoryService());
return instantiationService;
}
@@ -1938,10 +1939,6 @@ export class TestRemoteAgentService implements IRemoteAgentService {
declare readonly _serviceBrand: undefined;
socketFactory: ISocketFactory = {
connect() { }
};
getConnection(): IRemoteAgentConnection | null { return null; }
async getEnvironment(): Promise<IRemoteAgentEnvironment | null> { return null; }
async getRawEnvironment(): Promise<IRemoteAgentEnvironment | null> { return null; }
+18 -1
View File
@@ -26,6 +26,23 @@ declare module 'vscode' {
constructor(host: string, port: number, connectionToken?: string);
}
export interface ManagedMessagePassing {
onDidReceiveMessage: Event<Uint8Array>;
onDidClose: Event<Error | undefined>;
onDidEnd: Event<void>;
send: (data: Uint8Array) => void;
end: () => void;
drain?: () => Thenable<void>;
}
export class ManagedResolvedAuthority {
readonly makeConnection: () => Thenable<ManagedMessagePassing>;
readonly connectionToken: string | undefined;
constructor(makeConnection: () => Thenable<ManagedMessagePassing>, connectionToken?: string);
}
export interface ResolvedOptions {
extensionHostEnv?: { [key: string]: string | null };
@@ -109,7 +126,7 @@ declare module 'vscode' {
Output = 2
}
export type ResolverResult = ResolvedAuthority & ResolvedOptions & TunnelInformation;
export type ResolverResult = (ResolvedAuthority | ManagedResolvedAuthority) & ResolvedOptions & TunnelInformation;
export class RemoteAuthorityResolverError extends Error {
static NotAvailable(message?: string, handled?: boolean): RemoteAuthorityResolverError;