From f5427eed53bec4db53f95bb02802c87ebcefc665 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 18 Apr 2023 14:51:14 -0700 Subject: [PATCH 01/16] remote: first cut at 'inline' remote resolvers For web, it seems the most feasible direction for resolvers as we make existing remote extensions 'web enabled' is to allow them to run in the extension host. However, in no case will there just be a simple websocket we can connect to ordinarily. This PR implements a first cut at 'inline' resolvers where messaging is done in the extension host. I have not yet tested them on web, where I think some more wiring is needed to mirror desktop. Also, resolution of URLs is not in yet. I think for this we'd want to do some service-worker -based 'loopback' approach to run requests inline in the remote connection, similar to what I did for tunnels... Resolvers are not yet run in a dedicated extension host, but I think that should happen, at least on web where resolvers will always(?) be 'inline'. Most of the actual changes are genericizing places where we specified the "host" and "port" previously into an enum. Additionally, instead of having a single ISocketFactory, there's now a collection of them, which the extension host manager registers into when a managed resolution happens. --- extensions/vscode-test-resolver/package.json | 5 + .../vscode-test-resolver/src/extension.ts | 89 ++++++++++++------ src/vs/base/common/async.ts | 28 +++--- src/vs/base/common/buffer.ts | 48 ++++++++++ src/vs/base/test/common/buffer.test.ts | 16 ++++ .../remote/browser/browserSocketFactory.ts | 6 +- .../browser/remoteAuthorityResolverService.ts | 53 ++++++++--- .../platform/remote/common/managedSocket.ts | 26 ++++++ .../remote/common/remoteAgentConnection.ts | 49 +++++----- .../remote/common/remoteAuthorityResolver.ts | 26 +++++- .../common/remoteSocketFactoryCollection.ts | 53 +++++++++++ .../remoteAuthorityResolverService.ts | 67 +++++--------- .../platform/remote/node/nodeSocketFactory.ts | 21 +---- src/vs/platform/tunnel/common/tunnel.ts | 4 +- src/vs/platform/tunnel/node/tunnelService.ts | 45 +++++---- .../api/browser/mainThreadExtensionService.ts | 41 ++++++++- .../workbench/api/common/extHost.api.impl.ts | 1 + .../workbench/api/common/extHost.protocol.ts | 9 ++ .../api/common/extHostExtensionService.ts | 92 ++++++++++++++++--- src/vs/workbench/api/common/extHostTypes.ts | 19 +++- src/vs/workbench/browser/web.main.ts | 12 ++- .../contrib/webview/browser/webviewElement.ts | 5 +- .../electron-sandbox/desktop.main.ts | 9 +- src/vs/workbench/electron-sandbox/window.ts | 3 +- .../test/browser/configurationEditing.test.ts | 2 +- .../test/browser/configurationService.test.ts | 19 ++-- .../extensions/browser/extensionService.ts | 2 +- .../common/abstractExtensionService.ts | 2 +- .../extensions/common/extHostCustomers.ts | 8 ++ .../extensions/common/extensionHostManager.ts | 68 +++++++++++++- .../extensions/common/extensionHostProxy.ts | 5 + .../extensions/common/remoteExtensionHost.ts | 38 ++++---- .../nativeExtensionService.ts | 31 ++++--- .../remote/browser/remoteAgentService.ts | 6 +- .../common/abstractRemoteAgentService.ts | 56 ++++++----- .../services/remote/common/managedSocket.ts | 91 ++++++++++++++++++ .../remote/common/remoteAgentService.ts | 4 +- .../remote/common/remoteExplorerService.ts | 5 +- .../electron-sandbox/remoteAgentService.ts | 11 ++- .../test/browser/workbenchTestServices.ts | 4 +- src/vscode-dts/vscode.proposed.resolvers.d.ts | 19 +++- 41 files changed, 826 insertions(+), 272 deletions(-) create mode 100644 src/vs/platform/remote/common/managedSocket.ts create mode 100644 src/vs/platform/remote/common/remoteSocketFactoryCollection.ts create mode 100644 src/vs/workbench/services/remote/common/managedSocket.ts diff --git a/extensions/vscode-test-resolver/package.json b/extensions/vscode-test-resolver/package.json index 167275aa275..921bbba555c 100644 --- a/extensions/vscode-test-resolver/package.json +++ b/extensions/vscode-test-resolver/package.json @@ -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", diff --git a/extensions/vscode-test-resolver/src/extension.ts b/extensions/vscode-test-resolver/src/extension.ts index 05fd267d2bc..46f95f14f1f 100644 --- a/extensions/vscode-test-resolver/src/extension.ts +++ b/extensions/vscode-test-resolver/src/extension.ts @@ -27,7 +27,30 @@ export function activate(context: vscode.ExtensionContext) { let connectionPaused = false; const connectionPausedEvent = new vscode.EventEmitter(); - function doResolve(_authority: string, progress: vscode.Progress<{ message?: string; increment?: number }>): Promise { + 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 { 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 => { + 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(); + const closeEmitter = new vscode.EventEmitter(); + const endEmitter = new vscode.EventEmitter(); + + 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, + dataHandler: d => remoteSocket.write(d), + endHandler: () => remoteSocket.end(), + }; + }, connectionToken)); + } + return new Promise((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 = (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 { return vscode.Uri.file(uri.path); }, - resolve(_authority: string): Thenable { + resolve(_authority: string): Thenable { 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' }); })); diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index c476468e7fe..f1af44098af 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -1406,6 +1406,11 @@ export class IntervalCounter { export type ValueCallback = (value: T | Promise) => void; +const enum DeferredOutcome { + Resolved, + Rejected +} + /** * Creates a promise whose resolution or rejection can be controlled imperatively. */ @@ -1413,19 +1418,22 @@ export class DeferredPromise { private completeCallback!: ValueCallback; 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; @@ -1440,7 +1448,7 @@ export class DeferredPromise { public complete(value: T) { return new Promise(resolve => { this.completeCallback(value); - this.resolved = true; + this.outcome = { outcome: DeferredOutcome.Resolved, value }; resolve(); }); } @@ -1448,17 +1456,13 @@ export class DeferredPromise { public error(err: unknown) { return new Promise(resolve => { this.errorCallback(err); - this.rejected = true; + this.outcome = { outcome: DeferredOutcome.Rejected, value: err }; resolve(); }); } public cancel() { - new Promise(resolve => { - this.errorCallback(new CancellationError()); - this.rejected = true; - resolve(); - }); + return this.error(new CancellationError()); } } diff --git a/src/vs/base/common/buffer.ts b/src/vs/base/common/buffer.ts index 765a788327b..ff61eb5c9e2 100644 --- a/src/vs/base/common/buffer.ts +++ b/src/vs/base/common/buffer.ts @@ -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 { diff --git a/src/vs/base/test/common/buffer.test.ts b/src/vs/base/test/common/buffer.test.ts index ae210a549b3..5a37943b658 100644 --- a/src/vs/base/test/common/buffer.test.ts +++ b/src/vs/base/test/common/buffer.test.ts @@ -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: diff --git a/src/vs/platform/remote/browser/browserSocketFactory.ts b/src/vs/platform/remote/browser/browserSocketFactory.ts index 0998b898ea9..b1069a7d1ee 100644 --- a/src/vs/platform/remote/browser/browserSocketFactory.ts +++ b/src/vs/platform/remote/browser/browserSocketFactory.ts @@ -10,7 +10,7 @@ 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 { RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode, WebSocketMessagingPassing } from 'vs/platform/remote/common/remoteAuthorityResolver'; export interface IWebSocketFactory { create(url: string, debugLabel: string): IWebSocket; @@ -265,14 +265,14 @@ class BrowserSocket implements ISocket { } -export class BrowserSocketFactory implements ISocketFactory { +export class BrowserSocketFactory implements ISocketFactory { 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 { + connect({ host, port }: WebSocketMessagingPassing, 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)); diff --git a/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts b/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts index 183790bd295..bdcf72ae0da 100644 --- a/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts +++ b/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts @@ -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, MessagePassingType, ResolvedAuthority, ResolvedOptions, ResolverResult, 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()); public readonly onDidChangeConnectionData = this._onDidChangeConnectionData.event; - private readonly _promiseCache = new Map>(); + private readonly _resolveAuthorityRequests = new Map>(); private readonly _cache = new Map(); private readonly _connectionToken: Promise | string | undefined; private readonly _connectionTokens: Map; + private readonly _isWorkbenchOptionsBasedResolution: boolean; constructor( + isWorkbenchOptionsBasedResolution: boolean, connectionToken: Promise | 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(); + this._isWorkbenchOptionsBasedResolution = isWorkbenchOptionsBasedResolution; if (resourceUriProvider) { RemoteAuthorities.setDelegate(resourceUriProvider); } @@ -42,15 +47,20 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot } async resolveAuthority(authority: string): Promise { - 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(); + 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 { + // 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.messaging, 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, messaging: { type: MessagePassingType.WebSocket, host: host, port: 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.messaging.type === MessagePassingType.WebSocket) { + // todo@connor4312 need to implement some kind of loopback for ext host based messaging + RemoteAuthorities.set(resolvedAuthority.authority, resolvedAuthority.messaging.host, resolvedAuthority.messaging.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 { diff --git a/src/vs/platform/remote/common/managedSocket.ts b/src/vs/platform/remote/common/managedSocket.ts new file mode 100644 index 00000000000..9cbf5f326e8 --- /dev/null +++ b/src/vs/platform/remote/common/managedSocket.ts @@ -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'); diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts index bbbddcc39d5..2b526520983 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -16,7 +16,7 @@ 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, ResolvedAuthorityMessagePassing } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts'; import { ISignService } from 'vs/platform/sign/common/sign'; @@ -71,15 +71,14 @@ export interface OKMessage { export type HandshakeMessage = AuthRequest | SignRequest | ConnectionTypeRequest | ErrorMessage | OKMessage; -interface ISimpleConnectionOptions { +interface ISimpleConnectionOptions { commit: string | undefined; quality: string | undefined; - host: string; - port: number; + connectTo: T; connectionToken: string | undefined; reconnectionToken: string; reconnectionProtocol: PersistentProtocol | null; - socketFactory: ISocketFactory; + socketFactory: ISocketFactory; signService: ISignService; logService: ILogService; } @@ -88,8 +87,8 @@ 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; +export interface ISocketFactory { + connect(connectTo: T, path: string, query: string, debugLabel: string, callback: IConnectCallback): void; } function createTimeoutCancellation(millis: number): CancellationToken { @@ -192,12 +191,12 @@ function readOneControlMessage(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 { +function createSocket(logService: ILogService, socketFactory: ISocketFactory, connectTo: T, path: string, query: string, debugConnectionType: string, debugLabel: string, timeoutCancellationToken: CancellationToken): Promise { const result = new PromiseWithTimeout(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) => { + socketFactory.connect(connectTo, path, query, debugLabel, (err: any, socket: ISocket | undefined) => { 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.`); @@ -237,14 +236,14 @@ function raceWithTimeoutCancellation(promise: Promise, timeoutCancellation return result.promise; } -async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptions, connectionType: ConnectionType, args: any | undefined, timeoutCancellationToken: CancellationToken): Promise<{ protocol: PersistentProtocol; ownsProtocol: boolean }> { +async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptions, connectionType: ConnectionType, args: any | undefined, timeoutCancellationToken: CancellationToken): Promise<{ protocol: PersistentProtocol; ownsProtocol: boolean }> { const logPrefix = connectLogPrefix(options, connectionType); options.logService.trace(`${logPrefix} 1/6. invoking socketFactory.connect().`); let socket: ISocket; try { - socket = await createSocket(options.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.socketFactory, 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,23 +388,22 @@ async function doConnectRemoteAgentTunnel(options: ISimpleConnectionOptions, sta return protocol; } -export interface IConnectionOptions { +export interface IConnectionOptions { commit: string | undefined; quality: string | undefined; - socketFactory: ISocketFactory; - addressProvider: IAddressProvider; + socketFactory: ISocketFactory; + addressProvider: IAddressProvider; signService: ISignService; logService: ILogService; ipcLogger: IIPCLogger | null; } -async function resolveConnectionOptions(options: IConnectionOptions, reconnectionToken: string, reconnectionProtocol: PersistentProtocol | null): Promise { - const { host, port, connectionToken } = await options.addressProvider.getAddress(); +async function resolveConnectionOptions(options: IConnectionOptions, reconnectionToken: string, reconnectionProtocol: PersistentProtocol | null): Promise> { + 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, @@ -415,14 +413,13 @@ async function resolveConnectionOptions(options: IConnectionOptions, reconnectio }; } -export interface IAddress { - host: string; - port: number; +export interface IAddress { + connectTo: T; connectionToken: string | undefined; } -export interface IAddressProvider { - getAddress(): Promise; +export interface IAddressProvider { + getAddress(): Promise>; } export async function connectRemoteAgentManagement(options: IConnectionOptions, remoteAuthority: string, clientId: string): Promise { @@ -448,7 +445,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(options: IConnectionOptions, connectionFactory: (simpleOptions: ISimpleConnectionOptions) => Promise): Promise { +async function createInitialConnection(options: IConnectionOptions, connectionFactory: (simpleOptions: ISimpleConnectionOptions) => Promise): Promise { const MAX_ATTEMPTS = 5; for (let attempt = 1; ; attempt++) { @@ -691,7 +688,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 +829,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 { diff --git a/src/vs/platform/remote/common/remoteAuthorityResolver.ts b/src/vs/platform/remote/common/remoteAuthorityResolver.ts index d90895652e6..5f61474ff1d 100644 --- a/src/vs/platform/remote/common/remoteAuthorityResolver.ts +++ b/src/vs/platform/remote/common/remoteAuthorityResolver.ts @@ -10,10 +10,29 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' export const IRemoteAuthorityResolverService = createDecorator('remoteAuthorityResolverService'); +export const enum MessagePassingType { + WebSocket, + Managed +} + +export interface ManagedMessagingPassing { + type: MessagePassingType.Managed; + id: number; +} + +export interface WebSocketMessagingPassing { + type: MessagePassingType.WebSocket; + host: string; + port: number; +} + +export type ResolvedAuthorityMessagePassing = WebSocketMessagingPassing | ManagedMessagingPassing; + +export type MessagePassingOfType = ResolvedAuthorityMessagePassing & { type: T }; + export interface ResolvedAuthority { readonly authority: string; - readonly host: string; - readonly port: number; + readonly messaging: ResolvedAuthorityMessagePassing; readonly connectionToken: string | undefined; } @@ -50,8 +69,7 @@ export interface ResolverResult { } export interface IRemoteConnectionData { - host: string; - port: number; + connectTo: ResolvedAuthorityMessagePassing; connectionToken: string | undefined; } diff --git a/src/vs/platform/remote/common/remoteSocketFactoryCollection.ts b/src/vs/platform/remote/common/remoteSocketFactoryCollection.ts new file mode 100644 index 00000000000..d002d289a49 --- /dev/null +++ b/src/vs/platform/remote/common/remoteSocketFactoryCollection.ts @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { mapFind } from 'vs/base/common/arrays'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection'; +import { MessagePassingOfType, MessagePassingType, ResolvedAuthorityMessagePassing } from 'vs/platform/remote/common/remoteAuthorityResolver'; + +export const IRemoteSocketFactoryCollection = createDecorator('remoteSocketFactoryCollection'); + +export interface IRemoteSocketFactoryCollection { + 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( + type: T, + factory: (messagePassing: MessagePassingOfType) => ISocketFactory> | undefined + ): void; + + /** + * Gets a socket factory for the given message passing data. + */ + create(messagePassing: T): ISocketFactory | undefined; +} + +export class RemoteSocketFactoryCollection implements IRemoteSocketFactoryCollection { + declare readonly _serviceBrand: undefined; + + private readonly factories: { [T in MessagePassingType]?: ((messagePassing: MessagePassingOfType) => ISocketFactory> | undefined)[] } = {}; + + + public register( + type: T, + factory: (messagePassing: MessagePassingOfType) => ISocketFactory> | undefined + ): void { + this.factories[type] ??= []; + this.factories[type]!.push(factory); + } + + public create(messagePassing: T): ISocketFactory | undefined { + return mapFind( + (this.factories[messagePassing.type] || []) as ((messagePassing: T) => ISocketFactory | undefined)[], + factory => factory(messagePassing), + ); + } +} diff --git a/src/vs/platform/remote/electron-sandbox/remoteAuthorityResolverService.ts b/src/vs/platform/remote/electron-sandbox/remoteAuthorityResolverService.ts index f048d7153a9..f51ca6b3a13 100644 --- a/src/vs/platform/remote/electron-sandbox/remoteAuthorityResolverService.ts +++ b/src/vs/platform/remote/electron-sandbox/remoteAuthorityResolverService.ts @@ -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, MessagePassingType, ResolvedAuthority, ResolvedOptions, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts'; -class PendingPromise { - public readonly promise: Promise; - 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((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()); public readonly onDidChangeConnectionData = this._onDidChangeConnectionData.event; - private readonly _resolveAuthorityRequests: Map>; + private readonly _resolveAuthorityRequests: Map>; private readonly _connectionTokens: Map; - private readonly _canonicalURIRequests: Map>; + private readonly _canonicalURIRequests: Map>; private _canonicalURIProvider: ((uri: URI) => Promise) | null; constructor(@IProductService productService: IProductService) { super(); - this._resolveAuthorityRequests = new Map>(); + this._resolveAuthorityRequests = new Map>(); this._connectionTokens = new Map(); - this._canonicalURIRequests = new Map>(); + this._canonicalURIRequests = new Map>(); this._canonicalURIProvider = null; RemoteAuthorities.setServerRootPath(getRemoteServerRootPath(productService)); @@ -62,19 +37,19 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot resolveAuthority(authority: string): Promise { if (!this._resolveAuthorityRequests.has(authority)) { - this._resolveAuthorityRequests.set(authority, new PendingPromise(authority)); + this._resolveAuthorityRequests.set(authority, new DeferredPromise()); } - return this._resolveAuthorityRequests.get(authority)!.promise; + return this._resolveAuthorityRequests.get(authority)!.p; } async getCanonicalURI(uri: URI): Promise { const key = uri.toString(); if (!this._canonicalURIRequests.has(key)) { - const request = new PendingPromise(uri); - this._canonicalURIProvider?.(request.input).then((uri) => request.resolve(uri), (err) => request.reject(err)); + const request = new DeferredPromise(); + this._canonicalURIProvider?.(uri).then((uri) => request.complete(uri), (err) => request.error(err)); this._canonicalURIRequests.set(key, request); } - return this._canonicalURIRequests.get(key)!.promise; + return this._canonicalURIRequests.get(key)!.p; } getConnectionData(authority: string): IRemoteConnectionData | null { @@ -82,20 +57,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.messaging, 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 +77,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.messaging.type === MessagePassingType.WebSocket) { + // todo@connor4312 need to implement some kind of loopback for ext host based messaging + RemoteAuthorities.set(resolvedAuthority.authority, resolvedAuthority.messaging.host, resolvedAuthority.messaging.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 +93,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 +105,8 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot _setCanonicalURIProvider(provider: (uri: URI) => Promise): void { this._canonicalURIProvider = provider; - this._canonicalURIRequests.forEach((value) => { - this._canonicalURIProvider!(value.input).then((uri) => value.resolve(uri), (err) => value.reject(err)); + this._canonicalURIRequests.forEach((value, key) => { + this._canonicalURIProvider!(URI.parse(key)).then((uri) => value.complete(uri), (err) => value.error(err)); }); } } diff --git a/src/vs/platform/remote/node/nodeSocketFactory.ts b/src/vs/platform/remote/node/nodeSocketFactory.ts index 8e859bcd2f6..06d2e52f684 100644 --- a/src/vs/platform/remote/node/nodeSocketFactory.ts +++ b/src/vs/platform/remote/node/nodeSocketFactory.ts @@ -5,29 +5,18 @@ import * as net from 'net'; import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; +import { makeRawSocketHeaders } from 'vs/platform/remote/common/managedSocket'; import { IConnectCallback, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection'; +import { WebSocketMessagingPassing } from 'vs/platform/remote/common/remoteAuthorityResolver'; -export const nodeSocketFactory = new class implements ISocketFactory { - connect(host: string, port: number, path: string, query: string, debugLabel: string, callback: IConnectCallback): void { +export const nodeSocketFactory = new class implements ISocketFactory { + connect({ host, port }: WebSocketMessagingPassing, path: string, query: string, debugLabel: string, callback: IConnectCallback): void { const errorListener = (err: any) => callback(err, undefined); const socket = net.createConnection({ host: host, port: port }, () => { socket.removeListener('error', errorListener); - // 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'); - - 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(); diff --git a/src/vs/platform/tunnel/common/tunnel.ts b/src/vs/platform/tunnel/common/tunnel.ts index 1e66f39f215..f00d2c5b81d 100644 --- a/src/vs/platform/tunnel/common/tunnel.ts +++ b/src/vs/platform/tunnel/common/tunnel.ts @@ -110,7 +110,7 @@ export interface ITunnel { export interface ISharedTunnelsService { readonly _serviceBrand: undefined; - openTunnel(authority: string, addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localHost: string, localPort?: number, elevateIfNeeded?: boolean, privacy?: string, protocol?: string): Promise | undefined; + openTunnel(authority: string, addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localHost: string, localPort?: number, elevateIfNeeded?: boolean, privacy?: string, protocol?: string): Promise | undefined; } export interface ITunnelService { @@ -126,7 +126,7 @@ export interface ITunnelService { readonly onAddedTunnelProvider: Event; canTunnel(uri: URI): boolean; - openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localHost?: string, localPort?: number, elevateIfNeeded?: boolean, privacy?: string, protocol?: string): Promise | undefined; + openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localHost?: string, localPort?: number, elevateIfNeeded?: boolean, privacy?: string, protocol?: string): Promise | undefined; getExistingTunnel(remoteHost: string, remotePort: number): Promise; setEnvironmentTunnel(remoteHost: string, remotePort: number, localAddress: string, privacy: string, protocol: string): void; closeTunnel(remoteHost: string, remotePort: number): Promise; diff --git a/src/vs/platform/tunnel/node/tunnelService.ts b/src/vs/platform/tunnel/node/tunnelService.ts index aa941b067e3..be0182819a6 100644 --- a/src/vs/platform/tunnel/node/tunnelService.ts +++ b/src/vs/platform/tunnel/node/tunnelService.ts @@ -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 { IRemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection'; async function createRemoteTunnel(options: IConnectionOptions, defaultTunnelHost: string, tunnelRemoteHost: string, tunnelRemotePort: number, tunnelLocalPort?: number): Promise { 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, + @IRemoteSocketFactoryCollection private readonly socketFactories: IRemoteSocketFactoryCollection, @ILogService logService: ILogService, @ISignService private readonly signService: ISignService, @IProductService private readonly productService: IProductService, @@ -179,32 +179,40 @@ export class BaseTunnelService extends AbstractTunnelService { return this.createWithProvider(this._tunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, privacy, protocol); } else { this.logService.trace(`ForwardedPorts: (TunnelService) Creating tunnel without provider ${remoteHost}:${remotePort} on local port ${localPort}.`); - const options: IConnectionOptions = { - commit: this.productService.commit, - quality: this.productService.quality, - socketFactory: this.socketFactory, - addressProvider, - signService: this.signService, - logService: this.logService, - ipcLogger: null - }; + return addressProvider.getAddress().then(address => { + const socketFactory = this.socketFactories.create(address.connectTo); + if (!socketFactory) { + throw new Error(`No socket factory found for ${address.connectTo}`); + } - const tunnel = createRemoteTunnel(options, localHost, remoteHost, remotePort, localPort); - this.logService.trace('ForwardedPorts: (TunnelService) Tunnel created without provider.'); - this.addTunnelToMap(remoteHost, remotePort, tunnel); - return tunnel; + const options: IConnectionOptions = { + commit: this.productService.commit, + quality: this.productService.quality, + socketFactory, + addressProvider, + signService: this.signService, + logService: this.logService, + ipcLogger: null + }; + + const tunnel = createRemoteTunnel(options, localHost, remoteHost, remotePort, localPort); + this.logService.trace('ForwardedPorts: (TunnelService) Tunnel created without provider.'); + this.addTunnelToMap(remoteHost, remotePort, tunnel); + return tunnel; + }); } } } export class TunnelService extends BaseTunnelService { public constructor( + @IRemoteSocketFactoryCollection socketFactories: IRemoteSocketFactoryCollection, @ILogService logService: ILogService, @ISignService signService: ISignService, @IProductService productService: IProductService, @IConfigurationService configurationService: IConfigurationService ) { - super(nodeSocketFactory, logService, signService, productService, configurationService); + super(socketFactories, logService, signService, productService, configurationService); } } @@ -213,6 +221,7 @@ export class SharedTunnelsService extends Disposable implements ISharedTunnelsSe private readonly _tunnelServices: Map = new Map(); public constructor( + @IRemoteSocketFactoryCollection protected readonly socketFactories: IRemoteSocketFactoryCollection, @ILogService protected readonly logService: ILogService, @IProductService private readonly productService: IProductService, @ISignService private readonly signService: ISignService, @@ -224,7 +233,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 { 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.socketFactories, this.logService, this.signService, this.productService, this.configurationService); this._register(tunnelService); this._tunnelServices.set(authority, tunnelService); tunnelService.onTunnelClosed(async () => { diff --git a/src/vs/workbench/api/browser/mainThreadExtensionService.ts b/src/vs/workbench/api/browser/mainThreadExtensionService.ts index d822db420e5..7e107299f50 100644 --- a/src/vs/workbench/api/browser/mainThreadExtensionService.ts +++ b/src/vs/workbench/api/browser/mainThreadExtensionService.ts @@ -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, MessagePassingType } 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'; @@ -25,7 +25,7 @@ import { ExtensionHostKind } from 'vs/workbench/services/extensions/common/exten import { IExtensionDescriptionDelta } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; 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 { extHostNamedCustomer, IExtHostContext, IInternalExtHostContext, IManagedSocketCallbacks } from 'vs/workbench/services/extensions/common/extHostCustomers'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { ITimerService } from 'vs/workbench/services/timer/browser/timerService'; @@ -34,6 +34,7 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha private readonly _extensionHostKind: ExtensionHostKind; private readonly _internalExtensionService: IInternalExtensionService; + private readonly _managedSocketCallbacks: IManagedSocketCallbacks; constructor( extHostContext: IExtHostContext, @@ -50,6 +51,7 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha const internalExtHostContext = (extHostContext); this._internalExtensionService = internalExtHostContext.internalExtensionService; + this._managedSocketCallbacks = internalExtHostContext.managedSocketCallbacks; internalExtHostContext._setExtensionHostProxy( new ExtensionHostProxy(extHostContext.getProxy(ExtHostContext.ExtHostExtensionService)) ); @@ -59,6 +61,18 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha public dispose(): void { } + $onDidRemoteSocketHaveData(id: number, data: VSBuffer): void { + this._managedSocketCallbacks.onDidRemoteSocketHaveData(id, data); + } + + $onDidRemoteSocketClose(id: number, error: string | undefined): void { + this._managedSocketCallbacks.onDidRemoteSocketClose(id, error ? new Error(error) : undefined); + } + + $onDidRemoteSocketEnd(id: number): void { + this._managedSocketCallbacks.onDidRemoteSocketEnd(id); + } + $getExtension(extensionId: string) { return this._extensionService.getExtension(extensionId); } @@ -199,8 +213,15 @@ class ExtensionHostProxy implements IExtensionHostProxy { private readonly _actual: ExtHostExtensionServiceShape ) { } - resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise { - return this._actual.$resolveAuthority(remoteAuthority, resolveAttempt); + async resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise { + const resolved = await this._actual.$resolveAuthority(remoteAuthority, resolveAttempt); + if (resolved.type === 'ok') { + resolved.value.authority.toString = function () { + return this.messaging.type === MessagePassingType.Managed ? `ManagedSocket#${this.messaging.id}` : `${this.messaging.host}:${this.messaging.type}`; + }; + } + + return resolved; } async getCanonicalURI(remoteAuthority: string, uri: URI): Promise { const uriComponents = await this._actual.$getCanonicalURI(remoteAuthority, uri); @@ -236,4 +257,16 @@ class ExtensionHostProxy implements IExtensionHostProxy { test_down(size: number): Promise { return this._actual.$test_down(size); } + openRemoteSocket(factoryId: number): Promise { + return this._actual.$openRemoteSocket(factoryId); + } + remoteSocketWrite(socketId: number, buffer: VSBuffer): void { + return this._actual.$remoteSocketWrite(socketId, buffer); + } + remoteSocketEnd(socketId: number): void { + return this._actual.$remoteSocketEnd(socketId); + } + remoteSocketDrain(socketId: number): Promise { + return this._actual.$remoteSocketDrain(socketId); + } } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 6bb3af739e5..0e45d2f6d4f 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1420,6 +1420,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, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 752767d7f0d..3005830d937 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1237,6 +1237,10 @@ export interface MainThreadExtensionServiceShape extends IDisposable { $onExtensionRuntimeError(extensionId: ExtensionIdentifier, error: SerializedError): void; $setPerformanceMarks(marks: performance.PerformanceMark[]): Promise; $asBrowserUri(uri: UriComponents): Promise; + + $onDidRemoteSocketHaveData(id: number, data: VSBuffer): void; + $onDidRemoteSocketClose(id: number, error: string | undefined): void; + $onDidRemoteSocketEnd(id: number): void; } export interface SCMProviderFeatures { @@ -1593,6 +1597,11 @@ export interface ExtHostExtensionServiceShape { $test_latency(n: number): Promise; $test_up(b: VSBuffer): Promise; $test_down(size: number): Promise; + + $openRemoteSocket(factoryId: number): Promise; + $remoteSocketWrite(socketId: number, buffer: VSBuffer): void; + $remoteSocketEnd(socketId: number): void; + $remoteSocketDrain(socketId: number): Promise; } export interface FileSystemEvents { diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index afe2939e5b8..ae134bad42b 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -8,7 +8,7 @@ import * as path from 'vs/base/common/path'; import * as performance from 'vs/base/common/performance'; import { originalFSPath, joinPath, extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; import { asPromise, Barrier, IntervalTimer, timeout } from 'vs/base/common/async'; -import { dispose, toDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; @@ -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, MessagePassingType } 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'; @@ -79,6 +79,8 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme readonly _serviceBrand: undefined; + private static remoteSocketIdCounter = 0; + abstract readonly extensionRuntime: ExtensionRuntime; private readonly _onDidChangeRemoteConnectionData = this._register(new Emitter()); @@ -118,6 +120,11 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme private _started: boolean; private _isTerminating: boolean = false; private _remoteConnectionData: IRemoteConnectionData | null; + private readonly _managedSocketFactories: Map Thenable>; + private readonly _managedRemoteSockets: Map; constructor( @IInstantiationService instaService: IInstantiationService, @@ -191,6 +198,8 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme this._resolvers = Object.create(null); this._started = false; this._remoteConnectionData = this._initData.remote.connectionData; + this._managedSocketFactories = new Map(); + this._managedRemoteSockets = new Map(); } public getRemoteConnectionData(): IRemoteConnectionData | null { @@ -822,30 +831,44 @@ 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, + messaging: { type: MessagePassingType.WebSocket, host: result.host, port: result.port }, + connectionToken: result.connectionToken + }; + } else { + const factoryId = AbstractExtHostExtensionService.remoteSocketIdCounter++; + this._managedSocketFactories.set(factoryId, result.makeConnection); + authority = { + authority: remoteAuthority, + messaging: { type: MessagePassingType.Managed, id: factoryId }, + connectionToken: result.connectionToken + }; + } + return { type: 'ok', value: { authority, options, - tunnelInformation: { - environmentTunnels: result.environmentTunnels, - features: result.tunnelFeatures - } + tunnelInformation, } }; } catch (err) { @@ -866,6 +889,47 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme } } + public async $openRemoteSocket(factoryId: number): Promise { + const factory = this._managedSocketFactories.get(factoryId); + if (!factory) { + throw new Error(`No socket factory with id ${factoryId}`); + } + + const id = AbstractExtHostExtensionService.remoteSocketIdCounter++; + const socket = await factory(); + const disposable = new DisposableStore(); + + this._managedRemoteSockets.set(id, { object: socket, disposer: disposable }); + disposable.add(toDisposable(() => this._managedRemoteSockets.delete(id))); + disposable.add(socket.onDidEnd(() => { + this._mainThreadExtensionsProxy.$onDidRemoteSocketEnd(id); + disposable.dispose(); + })); + disposable.add(socket.onDidClose(e => { + this._mainThreadExtensionsProxy.$onDidRemoteSocketClose(id, e?.stack ?? e?.message); + disposable.dispose(); + })); + disposable.add(socket.onDidReceiveMessage(e => this._mainThreadExtensionsProxy.$onDidRemoteSocketHaveData(id, VSBuffer.wrap(e)))); + + return id; + } + + public $remoteSocketDrain(id: number): Promise { + return this._managedRemoteSockets.get(id)?.object.drainHandler?.() ?? Promise.resolve(); + } + + public $remoteSocketEnd(id: number): void { + const socket = this._managedRemoteSockets.get(id); + if (socket) { + socket.object.endHandler(); + socket.disposer.dispose(); + } + } + + public $remoteSocketWrite(id: number, buffer: VSBuffer): void { + this._managedRemoteSockets.get(id)?.object.dataHandler(buffer.buffer); + } + public async $getCanonicalURI(remoteAuthority: string, uriComponents: UriComponents): Promise { this._logService.info(`$getCanonicalURI invoked for authority (${getRemoteAuthorityPrefix(remoteAuthority)})`); diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index dbe449375e9..d5c91148172 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -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, public readonly connectionToken?: string) { + if (typeof connectionToken !== 'undefined') { + validateConnectionToken(connectionToken); + } + } +} + export class RemoteAuthorityResolverError extends Error { static NotAvailable(message?: string, handled?: boolean): RemoteAuthorityResolverError { diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 5625fb56d3b..13e8d1d26bc 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -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, MessagePassingType } 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 { IRemoteSocketFactoryCollection, RemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection'; +import { BrowserSocketFactory } from 'vs/platform/remote/browser/browserSocketFactory'; export class BrowserMain extends Disposable { @@ -253,7 +255,8 @@ export class BrowserMain extends Disposable { // Remote const connectionToken = environmentService.options.connectionToken || getCookieValue(connectionTokenCookieName); - const remoteAuthorityResolverService = new RemoteAuthorityResolverService(connectionToken, this.configuration.resourceUriProvider, productService, logService); + const expectResolverExtension = !!environmentService.remoteAuthority?.includes('+') && !environmentService.options.webSocketFactory; + const remoteAuthorityResolverService = new RemoteAuthorityResolverService(!expectResolverExtension, connectionToken, this.configuration.resourceUriProvider, productService, logService); serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService); // Signing @@ -292,7 +295,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 socketFactories = new RemoteSocketFactoryCollection(); + socketFactories.register(MessagePassingType.WebSocket, () => new BrowserSocketFactory(this.configuration.webSocketFactory)); + serviceCollection.set(IRemoteSocketFactoryCollection, socketFactories); + const remoteAgentService = this._register(new RemoteAgentService(socketFactories, userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService)); serviceCollection.set(IRemoteAgentService, remoteAgentService); await this.registerFileSystemProviders(environmentService, fileService, remoteAgentService, bufferLogger, logService, loggerService, logsPath); diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index ee19ad8de81..71e755072b1 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -782,7 +782,10 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD private async localLocalhost(id: string, origin: string) { const authority = this._environmentService.remoteAuthority; const resolveAuthority = authority ? await this._remoteAuthorityResolverService.resolveAuthority(authority) : undefined; - const redirect = resolveAuthority ? await this._portMappingManager.getRedirect(resolveAuthority.authority, origin) : undefined; + const redirect = resolveAuthority ? await this._portMappingManager.getRedirect({ + connectionToken: resolveAuthority.authority.connectionToken, + connectTo: resolveAuthority.authority.messaging, + }, origin) : undefined; return this._send('did-load-localhost', { id, origin, diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index eb348f8b08a..bba65653a0d 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -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, MessagePassingType } 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 { RemoteSocketFactoryCollection, IRemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection'; 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 socketFactories = new RemoteSocketFactoryCollection(); + socketFactories.register(MessagePassingType.WebSocket, () => new BrowserSocketFactory(null)); + serviceCollection.set(IRemoteSocketFactoryCollection, socketFactories); + const remoteAgentService = this._register(new RemoteAgentService(socketFactories, userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService)); serviceCollection.set(IRemoteAgentService, remoteAgentService); // Remote Files diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index cf6af9debb4..8432e212e74 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -842,7 +842,8 @@ export class NativeWindow extends Disposable { const remoteAuthority = this.environmentService.remoteAuthority; const addressProvider: IAddressProvider | undefined = remoteAuthority ? { getAddress: async (): Promise => { - return (await this.remoteAuthorityResolverService.resolveAuthority(remoteAuthority)).authority; + const { authority } = await this.remoteAuthorityResolverService.resolveAuthority(remoteAuthority); + return { connectTo: authority.messaging, connectionToken: authority.connectionToken }; } } : undefined; let tunnel = await this.tunnelService.getExistingTunnel(portMappingRequest.address, portMappingRequest.port); diff --git a/src/vs/workbench/services/configuration/test/browser/configurationEditing.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationEditing.test.ts index 3574a026ce5..058f5a99770 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationEditing.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationEditing.test.ts @@ -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); diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index ab13f504d76..43db57ad590 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -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 { RemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection'; 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 RemoteSocketFactoryCollection(), userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(false, undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService())); await (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 RemoteSocketFactoryCollection(), userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(false, undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService())); await (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 RemoteSocketFactoryCollection(), userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(false, undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService())); await (testObject).initialize(convertToWorkspacePayload(folder)); @@ -199,7 +200,7 @@ suite('WorkspaceContextService - Workspace', () => { const instantiationService = 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 = 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 = 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); @@ -766,7 +767,7 @@ suite('WorkspaceConfigurationService - Folder', () => { instantiationService = 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); @@ -1562,7 +1563,7 @@ suite('WorkspaceConfigurationService - Profiles', () => { instantiationService = 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 = 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); diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index 91a8c0fb57b..e01650924e9 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -170,7 +170,7 @@ class BrowserExtensionHostFactory 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; } diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index b7db6b7c552..f97648c1aac 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -938,7 +938,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx }, _onExtensionRuntimeError: (extensionId: ExtensionIdentifier, err: Error): void => { return this._onExtensionRuntimeError(extensionId, err); - } + }, }; } diff --git a/src/vs/workbench/services/extensions/common/extHostCustomers.ts b/src/vs/workbench/services/extensions/common/extHostCustomers.ts index 0d3104fcded..34123e72b15 100644 --- a/src/vs/workbench/services/extensions/common/extHostCustomers.ts +++ b/src/vs/workbench/services/extensions/common/extHostCustomers.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { VSBuffer } from 'vs/base/common/buffer'; import { IDisposable } from 'vs/base/common/lifecycle'; import { BrandedService, IConstructorSignature } from 'vs/platform/instantiation/common/instantiation'; import { ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensionHostKind'; @@ -15,8 +16,15 @@ export interface IExtHostContext extends IRPCProtocol { readonly extensionHostKind: ExtensionHostKind; } +export interface IManagedSocketCallbacks { + onDidRemoteSocketHaveData(id: number, data: VSBuffer): void; + onDidRemoteSocketEnd(id: number): void; + onDidRemoteSocketClose(id: number, error: Error | undefined): void; +} + export interface IInternalExtHostContext extends IExtHostContext { readonly internalExtensionService: IInternalExtensionService; + readonly managedSocketCallbacks: IManagedSocketCallbacks; _setExtensionHostProxy(extensionHostProxy: IExtensionHostProxy): void; _setAllMainProxyIdentifiers(mainProxyIdentifiers: ProxyIdentifier[]): void; } diff --git a/src/vs/workbench/services/extensions/common/extensionHostManager.ts b/src/vs/workbench/services/extensions/common/extensionHostManager.ts index 7a1d76d77cc..deb26526677 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostManager.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostManager.ts @@ -11,13 +11,15 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { StopWatch } from 'vs/base/common/stopwatch'; import { URI } from 'vs/base/common/uri'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; +import { SocketCloseEvent, SocketCloseEventType } from 'vs/base/parts/ipc/common/ipc.net'; import * as nls from 'vs/nls'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { RemoteAuthorityResolverErrorCode, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { ManagedMessagingPassing, MessagePassingType, RemoteAuthorityResolverErrorCode, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { IRemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -29,6 +31,7 @@ import { ExtensionRunningLocation } from 'vs/workbench/services/extensions/commo import { ActivationKind, ExtensionActivationReason, ExtensionHostExtensions, ExtensionHostStartup, IExtensionHost, IInternalExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { Proxied, ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import { IRPCProtocolLogger, RPCProtocol, RequestInitiator, ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol'; +import { ManagedSocket } from 'vs/workbench/services/remote/common/managedSocket'; // Enable to see detailed message communication between window and extension host const LOG_EXTENSION_HOST_COMMUNICATION = false; @@ -85,6 +88,13 @@ type ExtensionHostStartupEvent = { errorStack?: string; }; + +interface RemoteSocketHalf { + onData: Emitter; + onClose: Emitter; + onEnd: Emitter; +} + class ExtensionHostManager extends Disposable implements IExtensionHostManager { public readonly onDidExit: Event<[number, string | null]>; @@ -102,6 +112,7 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { private readonly _extensionHost: IExtensionHost; private _proxy: Promise | null; private _hasStarted = false; + private readonly _remoteSockets = new Map(); public get kind(): ExtensionHostKind { return this._extensionHost.runningLocation.kind; @@ -115,6 +126,7 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { extensionHost: IExtensionHost, initialActivationEvents: string[], private readonly _internalExtensionService: IInternalExtensionService, + @IRemoteSocketFactoryCollection private readonly _remoteSocketFactoryCollection: IRemoteSocketFactoryCollection, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @@ -287,6 +299,23 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { //#region internal internalExtensionService: this._internalExtensionService, + managedSocketCallbacks: { + onDidRemoteSocketHaveData: (id, data) => { + this._remoteSockets.get(id)?.onData.fire(data); + }, + onDidRemoteSocketEnd: id => { + this._remoteSockets.get(id)?.onEnd.fire(); + this._remoteSockets.delete(id); + }, + onDidRemoteSocketClose: (id, error) => { + this._remoteSockets.get(id)?.onClose.fire({ + type: SocketCloseEventType.NodeSocketCloseEvent, + error, + hadError: !!error + }); + this._remoteSockets.delete(id); + }, + }, _setExtensionHostProxy: (value: IExtensionHostProxy): void => { extensionHostProxy = value; }, @@ -409,7 +438,10 @@ 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}`); + if (resolverResult.value.authority.messaging.type === MessagePassingType.Managed) { + this.registerManagedSocketFactory(resolverResult.value.authority.messaging, proxy); + } } else { logError(`returned an error`, resolverResult.error); } @@ -428,6 +460,38 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { } } + private registerManagedSocketFactory(messaging: ManagedMessagingPassing, proxy: IExtensionHostProxy) { + this._remoteSocketFactoryCollection.register(MessagePassingType.Managed, resolved => { + if (resolved.id !== messaging.id) { + return undefined; + } + + return { + connect: ({ id: factoryId }, path, query, debugLabel, callback) => { + proxy.openRemoteSocket(factoryId).then(socketId => { + const half: RemoteSocketHalf = { + onClose: new Emitter(), + onData: new Emitter(), + onEnd: new Emitter(), + }; + this._remoteSockets.set(socketId, half); + + ManagedSocket.connect(socketId, proxy, path, query, debugLabel, half) + .then( + socket => { + socket.onDidDispose(() => this._remoteSockets.delete(socketId)); + callback(undefined, socket); + }, + err => { + this._remoteSockets.delete(socketId); + callback(err, undefined); + }); + }).catch(err => callback(err, undefined)); + }, + }; + }); + } + public async getCanonicalURI(remoteAuthority: string, uri: URI): Promise { const proxy = await this._proxy; if (!proxy) { diff --git a/src/vs/workbench/services/extensions/common/extensionHostProxy.ts b/src/vs/workbench/services/extensions/common/extensionHostProxy.ts index e1c962a8b40..b6c8a8d24cc 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostProxy.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostProxy.ts @@ -42,4 +42,9 @@ export interface IExtensionHostProxy { test_latency(n: number): Promise; test_up(b: VSBuffer): Promise; test_down(size: number): Promise; + + openRemoteSocket(factoryId: number): Promise; + remoteSocketWrite(socketId: number, buffer: VSBuffer): void; + remoteSocketEnd(socketId: number): void; + remoteSocketDrain(socketId: number): Promise; } diff --git a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts index 3d929ec2b21..9a13531cd01 100644 --- a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts @@ -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 { IRemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection'; 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, + @IRemoteSocketFactoryCollection private readonly socketFactories: IRemoteSocketFactoryCollection, @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @@ -84,21 +85,26 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { } public start(): Promise { - 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 }; - } - }, - signService: this._signService, - logService: this._logService, - ipcLogger: null - }; return this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority).then((resolverResult) => { + const socketFactory = this.socketFactories.create(resolverResult.authority.messaging); + if (!socketFactory) { + throw new Error('No socket factory found for remote authority'); + } + + const options: IConnectionOptions = { + commit: this._productService.commit, + quality: this._productService.quality, + socketFactory, + addressProvider: { + getAddress: async () => { + const { authority } = await this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority); + return { connectTo: authority.messaging, connectionToken: authority.connectionToken }; + } + }, + signService: this._signService, + logService: this._logService, + ipcLogger: null + }; const startParams: IRemoteExtensionHostStartParams = { language: platform.language, diff --git a/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts b/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts index c801603d6de..f6d4912de61 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts @@ -29,7 +29,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, MessagePassingType, RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode, 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'; @@ -277,15 +277,22 @@ export class NativeExtensionService extends AbstractExtensionService implements const authorityPlusIndex = remoteAuthority.indexOf('+'); if (authorityPlusIndex === -1) { // This authority does not need to be resolved, simply parse the port number - const { host, port } = parseAuthorityWithPort(remoteAuthority); - return { - authority: { - authority: remoteAuthority, - host, - port, - connectionToken: undefined - } - }; + try { + const { host, port } = parseAuthorityWithPort(remoteAuthority); + return { + authority: { + authority: remoteAuthority, + messaging: { + type: MessagePassingType.WebSocket, + host, + port + }, + connectionToken: undefined + } + }; + } catch { + // continue + } } const localProcessExtensionHosts = this._getExtensionHostManagers(ExtensionHostKind.LocalProcess); @@ -391,7 +398,7 @@ export class NativeExtensionService extends AbstractExtensionService implements 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`); + this._logService.info(`resolveAuthority(${authorityPrefix}) returned '${result.authority}' after ${sw.elapsed()} ms`); return result; } catch (err) { performance.mark(`code/didResolveAuthorityError/${authorityPrefix}`); @@ -631,7 +638,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; } diff --git a/src/vs/workbench/services/remote/browser/remoteAgentService.ts b/src/vs/workbench/services/remote/browser/remoteAgentService.ts index 3cc54d49eea..8e4e4ec1b45 100644 --- a/src/vs/workbench/services/remote/browser/remoteAgentService.ts +++ b/src/vs/workbench/services/remote/browser/remoteAgentService.ts @@ -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 { IRemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection'; export class RemoteAgentService extends AbstractRemoteAgentService implements IRemoteAgentService { constructor( - webSocketFactory: IWebSocketFactory | null | undefined, + @IRemoteSocketFactoryCollection socketFactories: IRemoteSocketFactoryCollection, @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(socketFactories, userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService); } } diff --git a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts index 2b0ecd843a1..9a80b63ff7e 100644 --- a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts +++ b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts @@ -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 { IRemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection'; export abstract class AbstractRemoteAgentService extends Disposable implements IRemoteAgentService { declare readonly _serviceBrand: undefined; - public readonly socketFactory: ISocketFactory; private readonly _connection: IRemoteAgentConnection | null; private _environment: Promise | null; constructor( - socketFactory: ISocketFactory, + @IRemoteSocketFactoryCollection private readonly socketFactories: IRemoteSocketFactoryCollection, @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.socketFactories, 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 _socketFactories: IRemoteSocketFactoryCollection, private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, private readonly _signService: ISignService, private readonly _logService: ILogService @@ -193,28 +192,35 @@ class RemoteAgentConnection extends Disposable implements IRemoteAgentConnection private async _createConnection(): Promise> { let firstCall = true; - const options: IConnectionOptions = { - commit: this._commit, - quality: this._quality, - socketFactory: this._socketFactory, - addressProvider: { - getAddress: async () => { - if (firstCall) { - firstCall = false; - } else { - this._onReconnecting.fire(undefined); - } - const { authority } = await this._remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority); - return { host: authority.host, port: authority.port, connectionToken: authority.connectionToken }; - } - }, - signService: this._signService, - logService: this._logService, - ipcLogger: false ? new IPCLogger(`Local \u2192 Remote`, `Remote \u2192 Local`) : null - }; let connection: ManagementPersistentConnection; const start = Date.now(); try { + const { authority } = await this._remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority); + const socketFactory = this._socketFactories.create(authority.messaging); + if (!socketFactory) { + throw new Error(`No socket factory found for ${authority}`); + } + + const options: IConnectionOptions = { + commit: this._commit, + quality: this._quality, + socketFactory, + addressProvider: { + getAddress: async () => { + if (firstCall) { + firstCall = false; + } else { + this._onReconnecting.fire(undefined); + } + const { authority } = await this._remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority); + return { connectTo: authority.messaging, connectionToken: authority.connectionToken }; + } + }, + signService: this._signService, + logService: this._logService, + ipcLogger: false ? new IPCLogger(`Local \u2192 Remote`, `Remote \u2192 Local`) : null + }; + connection = this._register(await connectRemoteAgentManagement(options, this.remoteAuthority, `renderer`)); } finally { this._initialConnectionMs = Date.now() - start; diff --git a/src/vs/workbench/services/remote/common/managedSocket.ts b/src/vs/workbench/services/remote/common/managedSocket.ts new file mode 100644 index 00000000000..4ff860a8f44 --- /dev/null +++ b/src/vs/workbench/services/remote/common/managedSocket.ts @@ -0,0 +1,91 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { VSBuffer } from 'vs/base/common/buffer'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { ISocket, SocketCloseEvent, SocketDiagnostics, SocketDiagnosticsEventType } from 'vs/base/parts/ipc/common/ipc.net'; +import { makeRawSocketHeaders, socketRawEndHeaderSequence } from 'vs/platform/remote/common/managedSocket'; +import { IExtensionHostProxy } from 'vs/workbench/services/extensions/common/extensionHostProxy'; + +export class ManagedSocket extends Disposable implements ISocket { + public static connect( + socketId: number, + proxy: IExtensionHostProxy, + path: string, query: string, debugLabel: string, + + half: { + onClose: Emitter; + onData: Emitter; + onEnd: Emitter; + } + ): Promise { + 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((resolve, reject) => { + d.add(socket.onData(d => { + if (d.indexOf(socketRawEndHeaderSequence) !== -1) { + resolve(socket); + } + })); + + d.add(socket.onClose(err => reject(err ?? new Error('socket closed')))); + d.add(socket.onEnd(() => reject(new Error('socket ended')))); + }).finally(() => d.dispose()); + } + + public onData: Event; + public onClose: Event; + public onEnd: Event; + + private readonly didDisposeEmitter = this._register(new Emitter()); + public onDidDispose = this.didDisposeEmitter.event; + + private ended = false; + + private constructor( + private readonly socketId: number, + private readonly proxy: IExtensionHostProxy, + private readonly debugLabel: string, + onCloseEmitter: Emitter, + onDataEmitter: Emitter, + onEndEmitter: Emitter, + ) { + super(); + this.onClose = this._register(onCloseEmitter).event; + this.onData = this._register(onDataEmitter).event; + this.onEnd = this._register(onEndEmitter).event; + } + + write(buffer: VSBuffer): void { + this.proxy.remoteSocketWrite(this.socketId, buffer); + } + + end(): void { + this.ended = true; + this.proxy.remoteSocketEnd(this.socketId); + } + + drain(): Promise { + 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(); + } +} + diff --git a/src/vs/workbench/services/remote/common/remoteAgentService.ts b/src/vs/workbench/services/remote/common/remoteAgentService.ts index ab505eb11ce..147d9728c80 100644 --- a/src/vs/workbench/services/remote/common/remoteAgentService.ts +++ b/src/vs/workbench/services/remote/common/remoteAgentService.ts @@ -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('remoteAgentService'); @@ -16,8 +16,6 @@ export const IRemoteAgentService = createDecorator('remoteA export interface IRemoteAgentService { readonly _serviceBrand: undefined; - readonly socketFactory: ISocketFactory; - getConnection(): IRemoteAgentConnection | null; /** * Get the remote environment. In case of an error, returns `null`. diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index 7ce33fccd67..e3d3388c15b 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -621,7 +621,10 @@ export class TunnelModel extends Disposable { if (!existingTunnel) { const authority = this.environmentService.remoteAuthority; const addressProvider: IAddressProvider | undefined = authority ? { - getAddress: async () => { return (await this.remoteAuthorityResolverService.resolveAuthority(authority)).authority; } + getAddress: async () => { + const r = await this.remoteAuthorityResolverService.resolveAuthority(authority); + return { connectTo: r.authority.messaging, connectionToken: r.authority.connectionToken }; + } } : undefined; const key = makeAddress(tunnelProperties.remote.host, tunnelProperties.remote.port); diff --git a/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts b/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts index 85a638d5937..ded93640ace 100644 --- a/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts +++ b/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts @@ -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, MessagePassingType, 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 { IRemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection'; export class RemoteAgentService extends AbstractRemoteAgentService implements IRemoteAgentService { constructor( + @IRemoteSocketFactoryCollection socketFactoryCollection: IRemoteSocketFactoryCollection, @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(socketFactoryCollection, 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 !== MessagePassingType.WebSocket) { return null; } return URI.from({ scheme: 'http', - authority: `${connectionData.host}:${connectionData.port}`, + authority: `${connectionData.connectTo.host}:${connectionData.connectTo.port}`, path: `/version` }); } diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index efcae100a18..34a841eaed9 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -167,6 +167,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 { IRemoteSocketFactoryCollection, RemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection'; export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined, undefined, undefined, undefined, undefined); @@ -325,6 +326,7 @@ export function workbenchInstantiationService( instantiationService.stub(IWorkspaceTrustManagementService, new TestWorkspaceTrustManagementService()); instantiationService.stub(ITerminalInstanceService, new TestTerminalInstanceService()); instantiationService.stub(IElevatedFileService, new BrowserElevatedFileService()); + instantiationService.stub(IRemoteSocketFactoryCollection, new RemoteSocketFactoryCollection()); return instantiationService; } @@ -1937,7 +1939,7 @@ export class TestRemoteAgentService implements IRemoteAgentService { declare readonly _serviceBrand: undefined; - socketFactory: ISocketFactory = { + socketFactory: ISocketFactory = { connect() { } }; diff --git a/src/vscode-dts/vscode.proposed.resolvers.d.ts b/src/vscode-dts/vscode.proposed.resolvers.d.ts index c1c413bc31f..f35962c273d 100644 --- a/src/vscode-dts/vscode.proposed.resolvers.d.ts +++ b/src/vscode-dts/vscode.proposed.resolvers.d.ts @@ -26,6 +26,23 @@ declare module 'vscode' { constructor(host: string, port: number, connectionToken?: string); } + export interface ManagedMessagePassing { + onDidReceiveMessage: Event; + onDidClose: Event; + onDidEnd: Event; + + dataHandler: (data: Uint8Array) => void; + endHandler: () => void; + drainHandler?: () => void; + } + + export class ManagedResolvedAuthority { + readonly makeConnection: () => Thenable; + readonly connectionToken: string | undefined; + + constructor(makeConnection: () => Thenable, 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; From ddfcf374416ba2e0d69e682db60722be4526093c Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 19 Apr 2023 12:33:47 -0700 Subject: [PATCH 02/16] remote: make managed web resolvers work --- src/vs/workbench/browser/web.main.ts | 3 +- .../environment/browser/environmentService.ts | 10 + .../electron-sandbox/environmentService.ts | 3 + .../extensions/browser/extensionService.ts | 195 ++++++++++++++++-- 4 files changed, 191 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 13e8d1d26bc..11bb9824eaf 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -255,8 +255,7 @@ export class BrowserMain extends Disposable { // Remote const connectionToken = environmentService.options.connectionToken || getCookieValue(connectionTokenCookieName); - const expectResolverExtension = !!environmentService.remoteAuthority?.includes('+') && !environmentService.options.webSocketFactory; - const remoteAuthorityResolverService = new RemoteAuthorityResolverService(!expectResolverExtension, connectionToken, this.configuration.resourceUriProvider, productService, logService); + const remoteAuthorityResolverService = new RemoteAuthorityResolverService(!environmentService.expectsResolverExtension, connectionToken, this.configuration.resourceUriProvider, productService, logService); serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService); // Signing diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 553cbdeb5a5..4a8a93ff8bf 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -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; } diff --git a/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts b/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts index 77580ee035b..fec734ac73b 100644 --- a/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts +++ b/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts @@ -62,6 +62,9 @@ export class NativeWorkbenchEnvironmentService extends AbstractNativeEnvironment @memoize get remoteAuthority() { return this.configuration.remoteAuthority; } + @memoize + get expectsResolverExtension() { return !!this.configuration.remoteAuthority; } + @memoize get execPath() { return this.configuration.execPath; } diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index e01650924e9..e90093e49eb 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Schemas } from 'vs/base/common/network'; +import { StopWatch } from 'vs/base/common/stopwatch'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ExtensionKind } from 'vs/platform/environment/common/environment'; import { ExtensionIdentifier, ExtensionType, IExtension, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -14,34 +15,40 @@ import { IAutomatedWindow } 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, 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'; -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 { IResolveAuthorityErrorResult } from 'vs/workbench/services/extensions/common/extensionHostProxy'; 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'; export class ExtensionService extends AbstractExtensionService implements IExtensionService { + private _resolveAuthorityAttempt: number = 0; + constructor( @IInstantiationService instantiationService: IInstantiationService, @INotificationService notificationService: INotificationService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IBrowserWorkbenchEnvironmentService private readonly _browserEnvironmentService: IBrowserWorkbenchEnvironmentService, @ITelemetryService telemetryService: ITelemetryService, @IWorkbenchExtensionEnablementService extensionEnablementService: IWorkbenchExtensionEnablementService, @IFileService fileService: IFileService, @@ -55,16 +62,21 @@ export class ExtensionService extends AbstractExtensionService implements IExten @IRemoteAgentService remoteAgentService: IRemoteAgentService, @IRemoteExtensionsScannerService remoteExtensionsScannerService: IRemoteExtensionsScannerService, @ILifecycleService lifecycleService: ILifecycleService, - @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService, + @IRemoteAuthorityResolverService private readonly _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 +84,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten new BrowserExtensionHostKindPicker(logService), instantiationService, notificationService, - environmentService, + _browserEnvironmentService, telemetryService, extensionEnablementService, fileService, @@ -129,8 +141,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten return dedupExtensions(system, user, development, this._logService); } - protected async _resolveExtensions(): Promise { - // fetch the remote environment + protected async _resolveExtensionsDefault() { const [localExtensions, remoteExtensions] = await Promise.all([ this._scanWebExtensions(), this._remoteExtensionsScannerService.scanExtensions() @@ -139,6 +150,50 @@ export class ExtensionService extends AbstractExtensionService implements IExten return new ResolvedExtensions(localExtensions, remoteExtensions, /*hasLocalProcess*/false, /*allowRemoteExtensionsInLocalWebWorker*/true); } + protected async _resolveExtensions(): Promise { + 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 _onExtensionHostExit(code: number): void { // Dispose everything associated with the extension host this.stopExtensionHosts(); @@ -148,15 +203,107 @@ export class ExtensionService extends AbstractExtensionService implements IExten automatedWindow.codeAutomationExit(code); } } + + // impl + + private async _resolveAuthorityAgain(): Promise { + 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 _resolveAuthorityInitial(remoteAuthority: string): Promise { + 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 _resolveAuthorityWithLogging(remoteAuthority: string): Promise { + 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}' 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; + } + } + + private async _resolveAuthority(remoteAuthority: string): Promise { + const localWebWorkerExtensionHosts = this._getExtensionHostManagers(ExtensionHostKind.LocalWebWorker); + if (localWebWorkerExtensionHosts.length === 0) { + // no local process extension hosts + throw new Error(`Cannot resolve authority`); + } + + this._resolveAuthorityAttempt++; + const results = await Promise.all(localWebWorkerExtensionHosts.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); + } } class BrowserExtensionHostFactory implements IExtensionHostFactory { constructor( + private readonly _extensionsProposedApi: ExtensionsProposedApi, + private readonly _scanWebExtensions: () => Promise, private readonly _getExtensions: () => Promise, @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 { @@ -165,7 +312,7 @@ 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(); @@ -177,15 +324,27 @@ class BrowserExtensionHostFactory implements IExtensionHostFactory { } } - private _createLocalExtensionHostDataProvider(runningLocations: ExtensionRunningLocationTracker, desiredRunningLocation: ExtensionRunningLocation): IWebWorkerExtensionHostDataProvider { + private _createLocalExtensionHostDataProvider(runningLocations: ExtensionRunningLocationTracker, desiredRunningLocation: ExtensionRunningLocation, isInitialStart: boolean): IWebWorkerExtensionHostDataProvider { return { getInitData: async (): Promise => { - 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) + }; + } } }; } From df8de50fc471dd9e6f776caeacdf3417dfd9aab2 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Sun, 23 Apr 2023 20:47:21 +0200 Subject: [PATCH 03/16] Renames --- .../remote/browser/browserSocketFactory.ts | 6 ++--- .../browser/remoteAuthorityResolverService.ts | 10 ++++----- .../remote/common/remoteAgentConnection.ts | 20 ++++++++--------- .../remote/common/remoteAuthorityResolver.ts | 18 +++++++-------- ...ction.ts => remoteSocketFactoryService.ts} | 22 +++++++++---------- .../remoteAuthorityResolverService.ts | 8 +++---- .../platform/remote/node/nodeSocketFactory.ts | 6 ++--- src/vs/platform/tunnel/common/tunnel.ts | 4 ++-- src/vs/platform/tunnel/node/tunnelService.ts | 8 +++---- .../api/browser/mainThreadExtensionService.ts | 4 ++-- .../api/common/extHostExtensionService.ts | 6 ++--- src/vs/workbench/browser/web.main.ts | 10 ++++----- .../contrib/webview/browser/webviewElement.ts | 2 +- .../electron-sandbox/desktop.main.ts | 10 ++++----- src/vs/workbench/electron-sandbox/window.ts | 2 +- .../test/browser/configurationService.test.ts | 8 +++---- .../extensions/common/extensionHostManager.ts | 14 ++++++------ .../extensions/common/remoteExtensionHost.ts | 8 +++---- .../nativeExtensionService.ts | 6 ++--- .../remote/browser/remoteAgentService.ts | 4 ++-- .../common/abstractRemoteAgentService.ts | 10 ++++----- .../remote/common/remoteExplorerService.ts | 2 +- .../electron-sandbox/remoteAgentService.ts | 8 +++---- .../test/browser/workbenchTestServices.ts | 4 ++-- 24 files changed, 100 insertions(+), 100 deletions(-) rename src/vs/platform/remote/common/{remoteSocketFactoryCollection.ts => remoteSocketFactoryService.ts} (53%) diff --git a/src/vs/platform/remote/browser/browserSocketFactory.ts b/src/vs/platform/remote/browser/browserSocketFactory.ts index b1069a7d1ee..97a6e908a5b 100644 --- a/src/vs/platform/remote/browser/browserSocketFactory.ts +++ b/src/vs/platform/remote/browser/browserSocketFactory.ts @@ -10,7 +10,7 @@ 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, WebSocketMessagingPassing } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode, WebSocketRemoteConnection } from 'vs/platform/remote/common/remoteAuthorityResolver'; export interface IWebSocketFactory { create(url: string, debugLabel: string): IWebSocket; @@ -265,14 +265,14 @@ class BrowserSocket implements ISocket { } -export class BrowserSocketFactory implements ISocketFactory { +export class BrowserSocketFactory implements ISocketFactory { private readonly _webSocketFactory: IWebSocketFactory; constructor(webSocketFactory: IWebSocketFactory | null | undefined) { this._webSocketFactory = webSocketFactory || defaultWebSocketFactory; } - connect({ host, port }: WebSocketMessagingPassing, path: string, query: string, debugLabel: string, callback: IConnectCallback): void { + connect({ host, port }: WebSocketRemoteConnection, 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)); diff --git a/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts b/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts index bdcf72ae0da..8d7f38aa9cd 100644 --- a/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts +++ b/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts @@ -13,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, MessagePassingType, ResolvedAuthority, ResolvedOptions, ResolverResult, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { IRemoteAuthorityResolverService, IRemoteConnectionData, RemoteConnectionType, ResolvedAuthority, ResolvedOptions, ResolverResult, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { getRemoteServerRootPath, parseAuthorityWithOptionalPort } from 'vs/platform/remote/common/remoteHosts'; export class RemoteAuthorityResolverService extends Disposable implements IRemoteAuthorityResolverService { @@ -71,7 +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 { - connectTo: resolverResult.authority.messaging, + connectTo: resolverResult.authority.connectTo, connectionToken: connectionToken }; } @@ -86,7 +86,7 @@ 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, messaging: { type: MessagePassingType.WebSocket, host: host, port: port }, connectionToken } }; + const result: ResolverResult = { authority: { authority, connectTo: { type: RemoteConnectionType.WebSocket, host: host, port: port }, connectionToken } }; RemoteAuthorities.set(authority, host, port); this._cache.set(authority, result); this._onDidChangeConnectionData.fire(); @@ -104,9 +104,9 @@ 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)!; - if (resolvedAuthority.messaging.type === MessagePassingType.WebSocket) { + 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.messaging.host, resolvedAuthority.messaging.port); + RemoteAuthorities.set(resolvedAuthority.authority, resolvedAuthority.connectTo.host, resolvedAuthority.connectTo.port); } if (resolvedAuthority.connectionToken) { RemoteAuthorities.setConnectionToken(resolvedAuthority.authority, resolvedAuthority.connectionToken); diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts index 2b526520983..a1b1a92dc02 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -16,7 +16,7 @@ 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, ResolvedAuthorityMessagePassing } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { RemoteAuthorityResolverError, RemoteConnection } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts'; import { ISignService } from 'vs/platform/sign/common/sign'; @@ -71,7 +71,7 @@ export interface OKMessage { export type HandshakeMessage = AuthRequest | SignRequest | ConnectionTypeRequest | ErrorMessage | OKMessage; -interface ISimpleConnectionOptions { +interface ISimpleConnectionOptions { commit: string | undefined; quality: string | undefined; connectTo: T; @@ -87,7 +87,7 @@ export interface IConnectCallback { (err: any | undefined, socket: ISocket | undefined): void; } -export interface ISocketFactory { +export interface ISocketFactory { connect(connectTo: T, path: string, query: string, debugLabel: string, callback: IConnectCallback): void; } @@ -191,7 +191,7 @@ function readOneControlMessage(protocol: PersistentProtocol, timeoutCancellat return result.promise; } -function createSocket(logService: ILogService, socketFactory: ISocketFactory, connectTo: T, path: string, query: string, debugConnectionType: string, debugLabel: string, timeoutCancellationToken: CancellationToken): Promise { +function createSocket(logService: ILogService, socketFactory: ISocketFactory, connectTo: T, path: string, query: string, debugConnectionType: string, debugLabel: string, timeoutCancellationToken: CancellationToken): Promise { const result = new PromiseWithTimeout(timeoutCancellationToken); const sw = StopWatch.create(false); logService.info(`Creating a socket (${debugLabel})...`); @@ -236,7 +236,7 @@ function raceWithTimeoutCancellation(promise: Promise, timeoutCancellation return result.promise; } -async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptions, connectionType: ConnectionType, args: any | undefined, timeoutCancellationToken: CancellationToken): Promise<{ protocol: PersistentProtocol; ownsProtocol: boolean }> { +async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptions, connectionType: ConnectionType, args: any | undefined, timeoutCancellationToken: CancellationToken): Promise<{ protocol: PersistentProtocol; ownsProtocol: boolean }> { const logPrefix = connectLogPrefix(options, connectionType); options.logService.trace(`${logPrefix} 1/6. invoking socketFactory.connect().`); @@ -388,7 +388,7 @@ async function doConnectRemoteAgentTunnel(options: ISimpleConnectionOptions, sta return protocol; } -export interface IConnectionOptions { +export interface IConnectionOptions { commit: string | undefined; quality: string | undefined; socketFactory: ISocketFactory; @@ -398,7 +398,7 @@ export interface IConnectionOptions { ipcLogger: IIPCLogger | null; } -async function resolveConnectionOptions(options: IConnectionOptions, reconnectionToken: string, reconnectionProtocol: PersistentProtocol | null): Promise> { +async function resolveConnectionOptions(options: IConnectionOptions, reconnectionToken: string, reconnectionProtocol: PersistentProtocol | null): Promise> { const { connectTo, connectionToken } = await options.addressProvider.getAddress(); return { commit: options.commit, @@ -413,12 +413,12 @@ async function resolveConnectionOptions(options: IConnectionOptions, recon }; } -export interface IAddress { +export interface IAddress { connectTo: T; connectionToken: string | undefined; } -export interface IAddressProvider { +export interface IAddressProvider { getAddress(): Promise>; } @@ -445,7 +445,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(options: IConnectionOptions, connectionFactory: (simpleOptions: ISimpleConnectionOptions) => Promise): Promise { +async function createInitialConnection(options: IConnectionOptions, connectionFactory: (simpleOptions: ISimpleConnectionOptions) => Promise): Promise { const MAX_ATTEMPTS = 5; for (let attempt = 1; ; attempt++) { diff --git a/src/vs/platform/remote/common/remoteAuthorityResolver.ts b/src/vs/platform/remote/common/remoteAuthorityResolver.ts index 5f61474ff1d..f9c884c498c 100644 --- a/src/vs/platform/remote/common/remoteAuthorityResolver.ts +++ b/src/vs/platform/remote/common/remoteAuthorityResolver.ts @@ -10,29 +10,29 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' export const IRemoteAuthorityResolverService = createDecorator('remoteAuthorityResolverService'); -export const enum MessagePassingType { +export const enum RemoteConnectionType { WebSocket, Managed } -export interface ManagedMessagingPassing { - type: MessagePassingType.Managed; +export interface ManagedRemoteConnection { + type: RemoteConnectionType.Managed; id: number; } -export interface WebSocketMessagingPassing { - type: MessagePassingType.WebSocket; +export interface WebSocketRemoteConnection { + type: RemoteConnectionType.WebSocket; host: string; port: number; } -export type ResolvedAuthorityMessagePassing = WebSocketMessagingPassing | ManagedMessagingPassing; +export type RemoteConnection = WebSocketRemoteConnection | ManagedRemoteConnection; -export type MessagePassingOfType = ResolvedAuthorityMessagePassing & { type: T }; +export type RemoteConnectionOfType = RemoteConnection & { type: T }; export interface ResolvedAuthority { readonly authority: string; - readonly messaging: ResolvedAuthorityMessagePassing; + readonly connectTo: RemoteConnection; readonly connectionToken: string | undefined; } @@ -69,7 +69,7 @@ export interface ResolverResult { } export interface IRemoteConnectionData { - connectTo: ResolvedAuthorityMessagePassing; + connectTo: RemoteConnection; connectionToken: string | undefined; } diff --git a/src/vs/platform/remote/common/remoteSocketFactoryCollection.ts b/src/vs/platform/remote/common/remoteSocketFactoryService.ts similarity index 53% rename from src/vs/platform/remote/common/remoteSocketFactoryCollection.ts rename to src/vs/platform/remote/common/remoteSocketFactoryService.ts index d002d289a49..dbf4456312c 100644 --- a/src/vs/platform/remote/common/remoteSocketFactoryCollection.ts +++ b/src/vs/platform/remote/common/remoteSocketFactoryService.ts @@ -6,11 +6,11 @@ import { mapFind } from 'vs/base/common/arrays'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection'; -import { MessagePassingOfType, MessagePassingType, ResolvedAuthorityMessagePassing } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { RemoteConnectionOfType, RemoteConnectionType, RemoteConnection } from 'vs/platform/remote/common/remoteAuthorityResolver'; -export const IRemoteSocketFactoryCollection = createDecorator('remoteSocketFactoryCollection'); +export const IRemoteSocketFactoryService = createDecorator('remoteSocketFactoryService'); -export interface IRemoteSocketFactoryCollection { +export interface IRemoteSocketFactoryService { readonly _serviceBrand: undefined; /** @@ -19,32 +19,32 @@ export interface IRemoteSocketFactoryCollection { * @param factory function that returns the socket factory, or undefined if * it can't handle the data. */ - register( + register( type: T, - factory: (messagePassing: MessagePassingOfType) => ISocketFactory> | undefined + factory: (messagePassing: RemoteConnectionOfType) => ISocketFactory> | undefined ): void; /** * Gets a socket factory for the given message passing data. */ - create(messagePassing: T): ISocketFactory | undefined; + create(messagePassing: T): ISocketFactory | undefined; } -export class RemoteSocketFactoryCollection implements IRemoteSocketFactoryCollection { +export class RemoteSocketFactoryService implements IRemoteSocketFactoryService { declare readonly _serviceBrand: undefined; - private readonly factories: { [T in MessagePassingType]?: ((messagePassing: MessagePassingOfType) => ISocketFactory> | undefined)[] } = {}; + private readonly factories: { [T in RemoteConnectionType]?: ((messagePassing: RemoteConnectionOfType) => ISocketFactory> | undefined)[] } = {}; - public register( + public register( type: T, - factory: (messagePassing: MessagePassingOfType) => ISocketFactory> | undefined + factory: (messagePassing: RemoteConnectionOfType) => ISocketFactory> | undefined ): void { this.factories[type] ??= []; this.factories[type]!.push(factory); } - public create(messagePassing: T): ISocketFactory | undefined { + public create(messagePassing: T): ISocketFactory | undefined { return mapFind( (this.factories[messagePassing.type] || []) as ((messagePassing: T) => ISocketFactory | undefined)[], factory => factory(messagePassing), diff --git a/src/vs/platform/remote/electron-sandbox/remoteAuthorityResolverService.ts b/src/vs/platform/remote/electron-sandbox/remoteAuthorityResolverService.ts index f51ca6b3a13..744b5970583 100644 --- a/src/vs/platform/remote/electron-sandbox/remoteAuthorityResolverService.ts +++ b/src/vs/platform/remote/electron-sandbox/remoteAuthorityResolverService.ts @@ -10,7 +10,7 @@ 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, MessagePassingType, 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'; export class RemoteAuthorityResolverService extends Disposable implements IRemoteAuthorityResolverService { @@ -62,7 +62,7 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot } const connectionToken = this._connectionTokens.get(authority); return { - connectTo: request.value!.authority.messaging, + connectTo: request.value!.authority.connectTo, connectionToken: connectionToken }; } @@ -77,9 +77,9 @@ 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)!; - if (resolvedAuthority.messaging.type === MessagePassingType.WebSocket) { + 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.messaging.host, resolvedAuthority.messaging.port); + RemoteAuthorities.set(resolvedAuthority.authority, resolvedAuthority.connectTo.host, resolvedAuthority.connectTo.port); } if (resolvedAuthority.connectionToken) { RemoteAuthorities.setConnectionToken(resolvedAuthority.authority, resolvedAuthority.connectionToken); diff --git a/src/vs/platform/remote/node/nodeSocketFactory.ts b/src/vs/platform/remote/node/nodeSocketFactory.ts index 06d2e52f684..9f1e7357d0a 100644 --- a/src/vs/platform/remote/node/nodeSocketFactory.ts +++ b/src/vs/platform/remote/node/nodeSocketFactory.ts @@ -7,10 +7,10 @@ import * as net from 'net'; import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; import { makeRawSocketHeaders } from 'vs/platform/remote/common/managedSocket'; import { IConnectCallback, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection'; -import { WebSocketMessagingPassing } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { WebSocketRemoteConnection } from 'vs/platform/remote/common/remoteAuthorityResolver'; -export const nodeSocketFactory = new class implements ISocketFactory { - connect({ host, port }: WebSocketMessagingPassing, path: string, query: string, debugLabel: string, callback: IConnectCallback): void { +export const nodeSocketFactory = new class implements ISocketFactory { + connect({ host, port }: WebSocketRemoteConnection, path: string, query: string, debugLabel: string, callback: IConnectCallback): void { const errorListener = (err: any) => callback(err, undefined); const socket = net.createConnection({ host: host, port: port }, () => { diff --git a/src/vs/platform/tunnel/common/tunnel.ts b/src/vs/platform/tunnel/common/tunnel.ts index f00d2c5b81d..1e66f39f215 100644 --- a/src/vs/platform/tunnel/common/tunnel.ts +++ b/src/vs/platform/tunnel/common/tunnel.ts @@ -110,7 +110,7 @@ export interface ITunnel { export interface ISharedTunnelsService { readonly _serviceBrand: undefined; - openTunnel(authority: string, addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localHost: string, localPort?: number, elevateIfNeeded?: boolean, privacy?: string, protocol?: string): Promise | undefined; + openTunnel(authority: string, addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localHost: string, localPort?: number, elevateIfNeeded?: boolean, privacy?: string, protocol?: string): Promise | undefined; } export interface ITunnelService { @@ -126,7 +126,7 @@ export interface ITunnelService { readonly onAddedTunnelProvider: Event; canTunnel(uri: URI): boolean; - openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localHost?: string, localPort?: number, elevateIfNeeded?: boolean, privacy?: string, protocol?: string): Promise | undefined; + openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localHost?: string, localPort?: number, elevateIfNeeded?: boolean, privacy?: string, protocol?: string): Promise | undefined; getExistingTunnel(remoteHost: string, remotePort: number): Promise; setEnvironmentTunnel(remoteHost: string, remotePort: number, localAddress: string, privacy: string, protocol: string): void; closeTunnel(remoteHost: string, remotePort: number): Promise; diff --git a/src/vs/platform/tunnel/node/tunnelService.ts b/src/vs/platform/tunnel/node/tunnelService.ts index be0182819a6..3c8c0840856 100644 --- a/src/vs/platform/tunnel/node/tunnelService.ts +++ b/src/vs/platform/tunnel/node/tunnelService.ts @@ -17,7 +17,7 @@ import { connectRemoteAgentTunnel, IAddressProvider, IConnectionOptions } from ' 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 { IRemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection'; +import { IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService'; async function createRemoteTunnel(options: IConnectionOptions, defaultTunnelHost: string, tunnelRemoteHost: string, tunnelRemotePort: number, tunnelLocalPort?: number): Promise { let readyTunnel: NodeRemoteTunnel | undefined; @@ -155,7 +155,7 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel { export class BaseTunnelService extends AbstractTunnelService { public constructor( - @IRemoteSocketFactoryCollection private readonly socketFactories: IRemoteSocketFactoryCollection, + @IRemoteSocketFactoryService private readonly socketFactories: IRemoteSocketFactoryService, @ILogService logService: ILogService, @ISignService private readonly signService: ISignService, @IProductService private readonly productService: IProductService, @@ -206,7 +206,7 @@ export class BaseTunnelService extends AbstractTunnelService { export class TunnelService extends BaseTunnelService { public constructor( - @IRemoteSocketFactoryCollection socketFactories: IRemoteSocketFactoryCollection, + @IRemoteSocketFactoryService socketFactories: IRemoteSocketFactoryService, @ILogService logService: ILogService, @ISignService signService: ISignService, @IProductService productService: IProductService, @@ -221,7 +221,7 @@ export class SharedTunnelsService extends Disposable implements ISharedTunnelsSe private readonly _tunnelServices: Map = new Map(); public constructor( - @IRemoteSocketFactoryCollection protected readonly socketFactories: IRemoteSocketFactoryCollection, + @IRemoteSocketFactoryService protected readonly socketFactories: IRemoteSocketFactoryService, @ILogService protected readonly logService: ILogService, @IProductService private readonly productService: IProductService, @ISignService private readonly signService: ISignService, diff --git a/src/vs/workbench/api/browser/mainThreadExtensionService.ts b/src/vs/workbench/api/browser/mainThreadExtensionService.ts index 7e107299f50..d06644993d4 100644 --- a/src/vs/workbench/api/browser/mainThreadExtensionService.ts +++ b/src/vs/workbench/api/browser/mainThreadExtensionService.ts @@ -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, MessagePassingType } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { IRemoteConnectionData, RemoteConnectionType } 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'; @@ -217,7 +217,7 @@ class ExtensionHostProxy implements IExtensionHostProxy { const resolved = await this._actual.$resolveAuthority(remoteAuthority, resolveAttempt); if (resolved.type === 'ok') { resolved.value.authority.toString = function () { - return this.messaging.type === MessagePassingType.Managed ? `ManagedSocket#${this.messaging.id}` : `${this.messaging.host}:${this.messaging.type}`; + return this.connectTo.type === RemoteConnectionType.Managed ? `ManagedSocket#${this.connectTo.id}` : `${this.connectTo.host}:${this.connectTo.type}`; }; } diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index ae134bad42b..a1ef48db4b2 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -26,7 +26,7 @@ import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet, IE import { VSBuffer } from 'vs/base/common/buffer'; import { ExtensionGlobalMemento, ExtensionMemento } from 'vs/workbench/api/common/extHostMemento'; import { RemoteAuthorityResolverError, ExtensionKind, ExtensionMode, ExtensionRuntime, ResolvedAuthority as ExtHostResolvedAuthority } from 'vs/workbench/api/common/extHostTypes'; -import { ResolvedAuthority, ResolvedOptions, RemoteAuthorityResolverErrorCode, IRemoteConnectionData, getRemoteAuthorityPrefix, TunnelInformation, MessagePassingType } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { ResolvedAuthority, ResolvedOptions, RemoteAuthorityResolverErrorCode, IRemoteConnectionData, getRemoteAuthorityPrefix, TunnelInformation, RemoteConnectionType } 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'; @@ -850,7 +850,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme if (result instanceof ExtHostResolvedAuthority) { authority = { authority: remoteAuthority, - messaging: { type: MessagePassingType.WebSocket, host: result.host, port: result.port }, + connectTo: { type: RemoteConnectionType.WebSocket, host: result.host, port: result.port }, connectionToken: result.connectionToken }; } else { @@ -858,7 +858,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme this._managedSocketFactories.set(factoryId, result.makeConnection); authority = { authority: remoteAuthority, - messaging: { type: MessagePassingType.Managed, id: factoryId }, + connectTo: { type: RemoteConnectionType.Managed, id: factoryId }, connectionToken: result.connectionToken }; } diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 13e8d1d26bc..b93bf2eb159 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -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, MessagePassingType } 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,7 +84,7 @@ 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 { IRemoteSocketFactoryCollection, RemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection'; +import { IRemoteSocketFactoryService, RemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService'; import { BrowserSocketFactory } from 'vs/platform/remote/browser/browserSocketFactory'; export class BrowserMain extends Disposable { @@ -295,9 +295,9 @@ export class BrowserMain extends Disposable { serviceCollection.set(IUserDataProfileService, userDataProfileService); // Remote Agent - const socketFactories = new RemoteSocketFactoryCollection(); - socketFactories.register(MessagePassingType.WebSocket, () => new BrowserSocketFactory(this.configuration.webSocketFactory)); - serviceCollection.set(IRemoteSocketFactoryCollection, socketFactories); + const socketFactories = new RemoteSocketFactoryService(); + socketFactories.register(RemoteConnectionType.WebSocket, () => new BrowserSocketFactory(this.configuration.webSocketFactory)); + serviceCollection.set(IRemoteSocketFactoryService, socketFactories); const remoteAgentService = this._register(new RemoteAgentService(socketFactories, userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService)); serviceCollection.set(IRemoteAgentService, remoteAgentService); diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index 71e755072b1..697b230d390 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -784,7 +784,7 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD const resolveAuthority = authority ? await this._remoteAuthorityResolverService.resolveAuthority(authority) : undefined; const redirect = resolveAuthority ? await this._portMappingManager.getRedirect({ connectionToken: resolveAuthority.authority.connectionToken, - connectTo: resolveAuthority.authority.messaging, + connectTo: resolveAuthority.authority.connectTo, }, origin) : undefined; return this._send('did-load-localhost', { id, diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index bba65653a0d..26992419253 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -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, MessagePassingType } 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'; @@ -56,7 +56,7 @@ import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/pol 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 { RemoteSocketFactoryCollection, IRemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection'; +import { RemoteSocketFactoryService, IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService'; export class DesktopMain extends Disposable { @@ -238,9 +238,9 @@ export class DesktopMain extends Disposable { serviceCollection.set(IUserDataProfileService, userDataProfileService); // Remote Agent - const socketFactories = new RemoteSocketFactoryCollection(); - socketFactories.register(MessagePassingType.WebSocket, () => new BrowserSocketFactory(null)); - serviceCollection.set(IRemoteSocketFactoryCollection, socketFactories); + const socketFactories = new RemoteSocketFactoryService(); + socketFactories.register(RemoteConnectionType.WebSocket, () => new BrowserSocketFactory(null)); + serviceCollection.set(IRemoteSocketFactoryService, socketFactories); const remoteAgentService = this._register(new RemoteAgentService(socketFactories, userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService)); serviceCollection.set(IRemoteAgentService, remoteAgentService); diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 8432e212e74..35847c8c46b 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -843,7 +843,7 @@ export class NativeWindow extends Disposable { const addressProvider: IAddressProvider | undefined = remoteAuthority ? { getAddress: async (): Promise => { const { authority } = await this.remoteAuthorityResolverService.resolveAuthority(remoteAuthority); - return { connectTo: authority.messaging, connectionToken: authority.connectionToken }; + return { connectTo: authority.connectTo, connectionToken: authority.connectionToken }; } } : undefined; let tunnel = await this.tunnelService.getExistingTunnel(portMappingRequest.address, portMappingRequest.port); diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index 43db57ad590..b0f307d212a 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -51,7 +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 { RemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection'; +import { RemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService'; function convertToWorkspacePayload(folder: URI): ISingleFolderWorkspaceIdentifier { return { @@ -90,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(new RemoteSocketFactoryCollection(), userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(false, 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 (testObject).initialize(convertToWorkspacePayload(folder)); }); @@ -133,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(new RemoteSocketFactoryCollection(), userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(false, 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 (testObject).initialize(convertToWorkspacePayload(folder)); const actual = testObject.getWorkspaceFolder(joinPath(folder, 'a')); @@ -156,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(new RemoteSocketFactoryCollection(), userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(false, 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 (testObject).initialize(convertToWorkspacePayload(folder)); diff --git a/src/vs/workbench/services/extensions/common/extensionHostManager.ts b/src/vs/workbench/services/extensions/common/extensionHostManager.ts index deb26526677..265b64f6160 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostManager.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostManager.ts @@ -18,8 +18,8 @@ import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { ManagedMessagingPassing, MessagePassingType, RemoteAuthorityResolverErrorCode, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { IRemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection'; +import { ManagedRemoteConnection, RemoteConnectionType, RemoteAuthorityResolverErrorCode, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -126,7 +126,7 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { extensionHost: IExtensionHost, initialActivationEvents: string[], private readonly _internalExtensionService: IInternalExtensionService, - @IRemoteSocketFactoryCollection private readonly _remoteSocketFactoryCollection: IRemoteSocketFactoryCollection, + @IRemoteSocketFactoryService private readonly _remoteSocketFactoryService: IRemoteSocketFactoryService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @@ -439,8 +439,8 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { intervalLogger.dispose(); if (resolverResult.type === 'ok') { logInfo(`returned ${resolverResult.value.authority}`); - if (resolverResult.value.authority.messaging.type === MessagePassingType.Managed) { - this.registerManagedSocketFactory(resolverResult.value.authority.messaging, proxy); + if (resolverResult.value.authority.connectTo.type === RemoteConnectionType.Managed) { + this.registerManagedSocketFactory(resolverResult.value.authority.connectTo, proxy); } } else { logError(`returned an error`, resolverResult.error); @@ -460,8 +460,8 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { } } - private registerManagedSocketFactory(messaging: ManagedMessagingPassing, proxy: IExtensionHostProxy) { - this._remoteSocketFactoryCollection.register(MessagePassingType.Managed, resolved => { + private registerManagedSocketFactory(messaging: ManagedRemoteConnection, proxy: IExtensionHostProxy) { + this._remoteSocketFactoryService.register(RemoteConnectionType.Managed, resolved => { if (resolved.id !== messaging.id) { return undefined; } diff --git a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts index 9a13531cd01..a79123f6cc5 100644 --- a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts @@ -18,7 +18,7 @@ import { ILogService, ILoggerService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { IConnectionOptions, IRemoteExtensionHostStartParams, connectRemoteAgentExtensionHost } from 'vs/platform/remote/common/remoteAgentConnection'; import { IRemoteAuthorityResolverService, IRemoteConnectionData } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { IRemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection'; +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'; @@ -62,7 +62,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { constructor( public readonly runningLocation: RemoteRunningLocation, private readonly _initDataProvider: IRemoteExtensionHostDataProvider, - @IRemoteSocketFactoryCollection private readonly socketFactories: IRemoteSocketFactoryCollection, + @IRemoteSocketFactoryService private readonly socketFactories: IRemoteSocketFactoryService, @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @@ -86,7 +86,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { public start(): Promise { return this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority).then((resolverResult) => { - const socketFactory = this.socketFactories.create(resolverResult.authority.messaging); + const socketFactory = this.socketFactories.create(resolverResult.authority.connectTo); if (!socketFactory) { throw new Error('No socket factory found for remote authority'); } @@ -98,7 +98,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { addressProvider: { getAddress: async () => { const { authority } = await this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority); - return { connectTo: authority.messaging, connectionToken: authority.connectionToken }; + return { connectTo: authority.connectTo, connectionToken: authority.connectionToken }; } }, signService: this._signService, diff --git a/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts b/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts index f6d4912de61..094b8d348e2 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts @@ -29,7 +29,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, MessagePassingType, RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode, ResolverResult, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { IRemoteAuthorityResolverService, RemoteConnectionType, RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode, 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'; @@ -282,8 +282,8 @@ export class NativeExtensionService extends AbstractExtensionService implements return { authority: { authority: remoteAuthority, - messaging: { - type: MessagePassingType.WebSocket, + connectTo: { + type: RemoteConnectionType.WebSocket, host, port }, diff --git a/src/vs/workbench/services/remote/browser/remoteAgentService.ts b/src/vs/workbench/services/remote/browser/remoteAgentService.ts index 8e4e4ec1b45..33535d03085 100644 --- a/src/vs/workbench/services/remote/browser/remoteAgentService.ts +++ b/src/vs/workbench/services/remote/browser/remoteAgentService.ts @@ -18,12 +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 { IRemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection'; +import { IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService'; export class RemoteAgentService extends AbstractRemoteAgentService implements IRemoteAgentService { constructor( - @IRemoteSocketFactoryCollection socketFactories: IRemoteSocketFactoryCollection, + @IRemoteSocketFactoryService socketFactories: IRemoteSocketFactoryService, @IUserDataProfileService userDataProfileService: IUserDataProfileService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IProductService productService: IProductService, diff --git a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts index 9a80b63ff7e..62eae669bf2 100644 --- a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts +++ b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts @@ -19,7 +19,7 @@ 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 { IRemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection'; +import { IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService'; export abstract class AbstractRemoteAgentService extends Disposable implements IRemoteAgentService { @@ -29,7 +29,7 @@ export abstract class AbstractRemoteAgentService extends Disposable implements I private _environment: Promise | null; constructor( - @IRemoteSocketFactoryCollection private readonly socketFactories: IRemoteSocketFactoryCollection, + @IRemoteSocketFactoryService private readonly socketFactories: IRemoteSocketFactoryService, @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, @IWorkbenchEnvironmentService protected readonly _environmentService: IWorkbenchEnvironmentService, @IProductService productService: IProductService, @@ -149,7 +149,7 @@ class RemoteAgentConnection extends Disposable implements IRemoteAgentConnection remoteAuthority: string, private readonly _commit: string | undefined, private readonly _quality: string | undefined, - private readonly _socketFactories: IRemoteSocketFactoryCollection, + private readonly _socketFactories: IRemoteSocketFactoryService, private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, private readonly _signService: ISignService, private readonly _logService: ILogService @@ -196,7 +196,7 @@ class RemoteAgentConnection extends Disposable implements IRemoteAgentConnection const start = Date.now(); try { const { authority } = await this._remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority); - const socketFactory = this._socketFactories.create(authority.messaging); + const socketFactory = this._socketFactories.create(authority.connectTo); if (!socketFactory) { throw new Error(`No socket factory found for ${authority}`); } @@ -213,7 +213,7 @@ class RemoteAgentConnection extends Disposable implements IRemoteAgentConnection this._onReconnecting.fire(undefined); } const { authority } = await this._remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority); - return { connectTo: authority.messaging, connectionToken: authority.connectionToken }; + return { connectTo: authority.connectTo, connectionToken: authority.connectionToken }; } }, signService: this._signService, diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index e3d3388c15b..45b26d67fd9 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -623,7 +623,7 @@ export class TunnelModel extends Disposable { const addressProvider: IAddressProvider | undefined = authority ? { getAddress: async () => { const r = await this.remoteAuthorityResolverService.resolveAuthority(authority); - return { connectTo: r.authority.messaging, connectionToken: r.authority.connectionToken }; + return { connectTo: r.authority.connectTo, connectionToken: r.authority.connectionToken }; } } : undefined; diff --git a/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts b/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts index ded93640ace..429e9dc926e 100644 --- a/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts +++ b/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { IRemoteAuthorityResolverService, MessagePassingType, 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 { AbstractRemoteAgentService } from 'vs/workbench/services/remote/common/abstractRemoteAgentService'; import { ISignService } from 'vs/platform/sign/common/sign'; @@ -20,11 +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 { IRemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection'; +import { IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService'; export class RemoteAgentService extends AbstractRemoteAgentService implements IRemoteAgentService { constructor( - @IRemoteSocketFactoryCollection socketFactoryCollection: IRemoteSocketFactoryCollection, + @IRemoteSocketFactoryService socketFactoryCollection: IRemoteSocketFactoryService, @IUserDataProfileService userDataProfileService: IUserDataProfileService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IProductService productService: IProductService, @@ -80,7 +80,7 @@ class RemoteConnectionFailureNotificationContribution implements IWorkbenchContr return null; } const connectionData = this._remoteAuthorityResolverService.getConnectionData(remoteAgentConnection.remoteAuthority); - if (!connectionData || connectionData.connectTo.type !== MessagePassingType.WebSocket) { + if (!connectionData || connectionData.connectTo.type !== RemoteConnectionType.WebSocket) { return null; } return URI.from({ diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 34a841eaed9..787b084d908 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -167,7 +167,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 { IRemoteSocketFactoryCollection, RemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection'; +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); @@ -326,7 +326,7 @@ export function workbenchInstantiationService( instantiationService.stub(IWorkspaceTrustManagementService, new TestWorkspaceTrustManagementService()); instantiationService.stub(ITerminalInstanceService, new TestTerminalInstanceService()); instantiationService.stub(IElevatedFileService, new BrowserElevatedFileService()); - instantiationService.stub(IRemoteSocketFactoryCollection, new RemoteSocketFactoryCollection()); + instantiationService.stub(IRemoteSocketFactoryService, new RemoteSocketFactoryService()); return instantiationService; } From 6cf3664f4fd80759f5ac63e261b50ebfc8a478f8 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Sun, 23 Apr 2023 23:16:23 +0200 Subject: [PATCH 04/16] Add `IRemoteSocketFactoryService.connect` --- .../remote/browser/browserSocketFactory.ts | 11 +++- .../browser/remoteAuthorityResolverService.ts | 4 +- .../remote/common/remoteAgentConnection.ts | 21 +++---- .../remote/common/remoteAuthorityResolver.ts | 28 ++++++--- .../common/remoteSocketFactoryService.ts | 46 ++++++++------- .../platform/remote/node/nodeSocketFactory.ts | 11 +++- src/vs/platform/tunnel/node/tunnelService.ts | 43 ++++++-------- .../api/common/extHostExtensionService.ts | 6 +- src/vs/workbench/browser/web.main.ts | 8 +-- .../electron-sandbox/desktop.main.ts | 8 +-- .../extensions/common/extensionHostManager.ts | 57 ++++++++++--------- .../extensions/common/remoteExtensionHost.ts | 35 +++++------- .../remote/browser/remoteAgentService.ts | 4 +- .../common/abstractRemoteAgentService.ts | 51 +++++++---------- .../remote/common/remoteExplorerService.ts | 5 +- .../test/browser/workbenchTestServices.ts | 5 -- 16 files changed, 171 insertions(+), 172 deletions(-) diff --git a/src/vs/platform/remote/browser/browserSocketFactory.ts b/src/vs/platform/remote/browser/browserSocketFactory.ts index 97a6e908a5b..c3b240af03d 100644 --- a/src/vs/platform/remote/browser/browserSocketFactory.ts +++ b/src/vs/platform/remote/browser/browserSocketFactory.ts @@ -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, WebSocketRemoteConnection } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { IConnectCallback, 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,13 +265,18 @@ class BrowserSocket implements ISocket { } -export class BrowserSocketFactory implements ISocketFactory { +export class BrowserSocketFactory implements ISocketFactory { + private readonly _webSocketFactory: IWebSocketFactory; constructor(webSocketFactory: IWebSocketFactory | null | undefined) { this._webSocketFactory = webSocketFactory || defaultWebSocketFactory; } + supports(connectTo: WebSocketRemoteConnection): boolean { + return true; + } + connect({ host, port }: WebSocketRemoteConnection, 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); diff --git a/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts b/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts index 8d7f38aa9cd..2e956666f15 100644 --- a/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts +++ b/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts @@ -13,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, RemoteConnectionType, ResolvedAuthority, ResolvedOptions, 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 { @@ -86,7 +86,7 @@ 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, connectTo: { type: RemoteConnectionType.WebSocket, host: host, port: port }, connectionToken } }; + const result: ResolverResult = { authority: { authority, connectTo: new WebSocketRemoteConnection(host, port), connectionToken } }; RemoteAuthorities.set(authority, host, port); this._cache.set(authority, result); this._onDidChangeConnectionData.fire(); diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts index a1b1a92dc02..7c66a0976e6 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -18,6 +18,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment'; 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 */; @@ -78,19 +79,11 @@ interface ISimpleConnectionOptions; + remoteSocketFactoryService: IRemoteSocketFactoryService; signService: ISignService; logService: ILogService; } -export interface IConnectCallback { - (err: any | undefined, socket: ISocket | undefined): void; -} - -export interface ISocketFactory { - connect(connectTo: T, path: string, query: string, debugLabel: string, callback: IConnectCallback): void; -} - function createTimeoutCancellation(millis: number): CancellationToken { const source = new CancellationTokenSource(); setTimeout(() => source.cancel(), millis); @@ -191,12 +184,12 @@ function readOneControlMessage(protocol: PersistentProtocol, timeoutCancellat return result.promise; } -function createSocket(logService: ILogService, socketFactory: ISocketFactory, connectTo: T, path: string, query: string, debugConnectionType: string, debugLabel: string, timeoutCancellationToken: CancellationToken): Promise { +function createSocket(logService: ILogService, remoteSocketFactoryService: IRemoteSocketFactoryService, connectTo: T, path: string, query: string, debugConnectionType: string, debugLabel: string, timeoutCancellationToken: CancellationToken): Promise { const result = new PromiseWithTimeout(timeoutCancellationToken); const sw = StopWatch.create(false); logService.info(`Creating a socket (${debugLabel})...`); performance.mark(`code/willCreateSocket/${debugConnectionType}`); - socketFactory.connect(connectTo, path, query, debugLabel, (err: any, socket: ISocket | undefined) => { + remoteSocketFactoryService.connect(connectTo, path, query, debugLabel, (err: any, socket: ISocket | undefined) => { 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.`); @@ -243,7 +236,7 @@ async function connectToRemoteExtensionHostAgent(opt let socket: ISocket; try { - socket = await createSocket(options.logService, options.socketFactory, options.connectTo, 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); @@ -391,8 +384,8 @@ async function doConnectRemoteAgentTunnel(options: ISimpleConnectionOptions, sta export interface IConnectionOptions { commit: string | undefined; quality: string | undefined; - socketFactory: ISocketFactory; addressProvider: IAddressProvider; + remoteSocketFactoryService: IRemoteSocketFactoryService; signService: ISignService; logService: ILogService; ipcLogger: IIPCLogger | null; @@ -407,7 +400,7 @@ async function resolveConnectionOptions(options: ICo connectionToken: connectionToken, reconnectionToken: reconnectionToken, reconnectionProtocol: reconnectionProtocol, - socketFactory: options.socketFactory, + remoteSocketFactoryService: options.remoteSocketFactoryService, signService: options.signService, logService: options.logService }; diff --git a/src/vs/platform/remote/common/remoteAuthorityResolver.ts b/src/vs/platform/remote/common/remoteAuthorityResolver.ts index f9c884c498c..c67541b0bc6 100644 --- a/src/vs/platform/remote/common/remoteAuthorityResolver.ts +++ b/src/vs/platform/remote/common/remoteAuthorityResolver.ts @@ -15,15 +15,29 @@ export const enum RemoteConnectionType { Managed } -export interface ManagedRemoteConnection { - type: RemoteConnectionType.Managed; - id: number; +export class ManagedRemoteConnection { + public readonly type = RemoteConnectionType.Managed; + + constructor( + public readonly id: number + ) { } + + public toString(): string { + return `Managed(${this.id})`; + } } -export interface WebSocketRemoteConnection { - type: RemoteConnectionType.WebSocket; - host: string; - port: number; +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; diff --git a/src/vs/platform/remote/common/remoteSocketFactoryService.ts b/src/vs/platform/remote/common/remoteSocketFactoryService.ts index dbf4456312c..b6d129a530a 100644 --- a/src/vs/platform/remote/common/remoteSocketFactoryService.ts +++ b/src/vs/platform/remote/common/remoteSocketFactoryService.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { mapFind } from 'vs/base/common/arrays'; +import { ISocket } from 'vs/base/parts/ipc/common/ipc.net'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection'; import { RemoteConnectionOfType, RemoteConnectionType, RemoteConnection } from 'vs/platform/remote/common/remoteAuthorityResolver'; export const IRemoteSocketFactoryService = createDecorator('remoteSocketFactoryService'); @@ -19,35 +18,40 @@ export interface IRemoteSocketFactoryService { * @param factory function that returns the socket factory, or undefined if * it can't handle the data. */ - register( - type: T, - factory: (messagePassing: RemoteConnectionOfType) => ISocketFactory> | undefined - ): void; + register(type: T, factory: ISocketFactory): void; - /** - * Gets a socket factory for the given message passing data. - */ - create(messagePassing: T): ISocketFactory | undefined; + connect(connectTo: RemoteConnection, path: string, query: string, debugLabel: string, callback: IConnectCallback): void; +} + +export interface ISocketFactory { + supports(connectTo: RemoteConnectionOfType): boolean; + connect(connectTo: RemoteConnectionOfType, path: string, query: string, debugLabel: string, callback: IConnectCallback): void; +} + +export interface IConnectCallback { + (err: any | undefined, socket: ISocket | undefined): void; } export class RemoteSocketFactoryService implements IRemoteSocketFactoryService { declare readonly _serviceBrand: undefined; - private readonly factories: { [T in RemoteConnectionType]?: ((messagePassing: RemoteConnectionOfType) => ISocketFactory> | undefined)[] } = {}; + private readonly factories: { [T in RemoteConnectionType]?: ISocketFactory[] } = {}; - - public register( - type: T, - factory: (messagePassing: RemoteConnectionOfType) => ISocketFactory> | undefined - ): void { + public register(type: T, factory: ISocketFactory): void { this.factories[type] ??= []; this.factories[type]!.push(factory); } - public create(messagePassing: T): ISocketFactory | undefined { - return mapFind( - (this.factories[messagePassing.type] || []) as ((messagePassing: T) => ISocketFactory | undefined)[], - factory => factory(messagePassing), - ); + private getSocketFactory(messagePassing: RemoteConnectionOfType): ISocketFactory | undefined { + const factories = (this.factories[messagePassing.type] || []) as ISocketFactory[]; + return factories.find(factory => factory.supports(messagePassing)); + } + + public connect(connectTo: RemoteConnection, path: string, query: string, debugLabel: string, callback: IConnectCallback): void { + const socketFactory = this.getSocketFactory(connectTo); + if (!socketFactory) { + throw new Error(`No socket factory found for ${connectTo}`); + } + return socketFactory.connect(connectTo, path, query, debugLabel, callback); } } diff --git a/src/vs/platform/remote/node/nodeSocketFactory.ts b/src/vs/platform/remote/node/nodeSocketFactory.ts index 9f1e7357d0a..8a2776b5d33 100644 --- a/src/vs/platform/remote/node/nodeSocketFactory.ts +++ b/src/vs/platform/remote/node/nodeSocketFactory.ts @@ -6,10 +6,15 @@ import * as net from 'net'; import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; import { makeRawSocketHeaders } from 'vs/platform/remote/common/managedSocket'; -import { IConnectCallback, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection'; -import { WebSocketRemoteConnection } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { RemoteConnectionType, WebSocketRemoteConnection } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { IConnectCallback, ISocketFactory } from 'vs/platform/remote/common/remoteSocketFactoryService'; + +export const nodeSocketFactory = new class implements ISocketFactory { + + supports(connectTo: WebSocketRemoteConnection): boolean { + return true; + } -export const nodeSocketFactory = new class implements ISocketFactory { connect({ host, port }: WebSocketRemoteConnection, path: string, query: string, debugLabel: string, callback: IConnectCallback): void { const errorListener = (err: any) => callback(err, undefined); diff --git a/src/vs/platform/tunnel/node/tunnelService.ts b/src/vs/platform/tunnel/node/tunnelService.ts index 3c8c0840856..31f153b651a 100644 --- a/src/vs/platform/tunnel/node/tunnelService.ts +++ b/src/vs/platform/tunnel/node/tunnelService.ts @@ -155,7 +155,7 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel { export class BaseTunnelService extends AbstractTunnelService { public constructor( - @IRemoteSocketFactoryService private readonly socketFactories: IRemoteSocketFactoryService, + @IRemoteSocketFactoryService private readonly remoteSocketFactoryService: IRemoteSocketFactoryService, @ILogService logService: ILogService, @ISignService private readonly signService: ISignService, @IProductService private readonly productService: IProductService, @@ -179,40 +179,33 @@ export class BaseTunnelService extends AbstractTunnelService { return this.createWithProvider(this._tunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, privacy, protocol); } else { this.logService.trace(`ForwardedPorts: (TunnelService) Creating tunnel without provider ${remoteHost}:${remotePort} on local port ${localPort}.`); - return addressProvider.getAddress().then(address => { - const socketFactory = this.socketFactories.create(address.connectTo); - if (!socketFactory) { - throw new Error(`No socket factory found for ${address.connectTo}`); - } + const options: IConnectionOptions = { + commit: this.productService.commit, + quality: this.productService.quality, + addressProvider, + remoteSocketFactoryService: this.remoteSocketFactoryService, + signService: this.signService, + logService: this.logService, + ipcLogger: null + }; - const options: IConnectionOptions = { - commit: this.productService.commit, - quality: this.productService.quality, - socketFactory, - addressProvider, - signService: this.signService, - logService: this.logService, - ipcLogger: null - }; - - const tunnel = createRemoteTunnel(options, localHost, remoteHost, remotePort, localPort); - this.logService.trace('ForwardedPorts: (TunnelService) Tunnel created without provider.'); - this.addTunnelToMap(remoteHost, remotePort, tunnel); - return tunnel; - }); + const tunnel = createRemoteTunnel(options, localHost, remoteHost, remotePort, localPort); + this.logService.trace('ForwardedPorts: (TunnelService) Tunnel created without provider.'); + this.addTunnelToMap(remoteHost, remotePort, tunnel); + return tunnel; } } } export class TunnelService extends BaseTunnelService { public constructor( - @IRemoteSocketFactoryService socketFactories: IRemoteSocketFactoryService, + @IRemoteSocketFactoryService remoteSocketFactoryService: IRemoteSocketFactoryService, @ILogService logService: ILogService, @ISignService signService: ISignService, @IProductService productService: IProductService, @IConfigurationService configurationService: IConfigurationService ) { - super(socketFactories, logService, signService, productService, configurationService); + super(remoteSocketFactoryService, logService, signService, productService, configurationService); } } @@ -221,7 +214,7 @@ export class SharedTunnelsService extends Disposable implements ISharedTunnelsSe private readonly _tunnelServices: Map = new Map(); public constructor( - @IRemoteSocketFactoryService protected readonly socketFactories: IRemoteSocketFactoryService, + @IRemoteSocketFactoryService protected readonly remoteSocketFactoryService: IRemoteSocketFactoryService, @ILogService protected readonly logService: ILogService, @IProductService private readonly productService: IProductService, @ISignService private readonly signService: ISignService, @@ -233,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 { this.logService.trace(`ForwardedPorts: (SharedTunnelService) openTunnel request for ${remoteHost}:${remotePort} on local port ${localPort}.`); if (!this._tunnelServices.has(authority)) { - const tunnelService = new TunnelService(this.socketFactories, 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 () => { diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index a1ef48db4b2..16548fdb328 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -26,7 +26,7 @@ import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet, IE import { VSBuffer } from 'vs/base/common/buffer'; import { ExtensionGlobalMemento, ExtensionMemento } from 'vs/workbench/api/common/extHostMemento'; import { RemoteAuthorityResolverError, ExtensionKind, ExtensionMode, ExtensionRuntime, ResolvedAuthority as ExtHostResolvedAuthority } from 'vs/workbench/api/common/extHostTypes'; -import { ResolvedAuthority, ResolvedOptions, RemoteAuthorityResolverErrorCode, IRemoteConnectionData, getRemoteAuthorityPrefix, TunnelInformation, RemoteConnectionType } from 'vs/platform/remote/common/remoteAuthorityResolver'; +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'; @@ -850,7 +850,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme if (result instanceof ExtHostResolvedAuthority) { authority = { authority: remoteAuthority, - connectTo: { type: RemoteConnectionType.WebSocket, host: result.host, port: result.port }, + connectTo: new WebSocketRemoteConnection(result.host, result.port), connectionToken: result.connectionToken }; } else { @@ -858,7 +858,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme this._managedSocketFactories.set(factoryId, result.makeConnection); authority = { authority: remoteAuthority, - connectTo: { type: RemoteConnectionType.Managed, id: factoryId }, + connectTo: new ManagedRemoteConnection(factoryId), connectionToken: result.connectionToken }; } diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index b93bf2eb159..3a4ca635b06 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -295,10 +295,10 @@ export class BrowserMain extends Disposable { serviceCollection.set(IUserDataProfileService, userDataProfileService); // Remote Agent - const socketFactories = new RemoteSocketFactoryService(); - socketFactories.register(RemoteConnectionType.WebSocket, () => new BrowserSocketFactory(this.configuration.webSocketFactory)); - serviceCollection.set(IRemoteSocketFactoryService, socketFactories); - const remoteAgentService = this._register(new RemoteAgentService(socketFactories, 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); await this.registerFileSystemProviders(environmentService, fileService, remoteAgentService, bufferLogger, logService, loggerService, logsPath); diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index 26992419253..b4514fd71b1 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -238,10 +238,10 @@ export class DesktopMain extends Disposable { serviceCollection.set(IUserDataProfileService, userDataProfileService); // Remote Agent - const socketFactories = new RemoteSocketFactoryService(); - socketFactories.register(RemoteConnectionType.WebSocket, () => new BrowserSocketFactory(null)); - serviceCollection.set(IRemoteSocketFactoryService, socketFactories); - const remoteAgentService = this._register(new RemoteAgentService(socketFactories, 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 diff --git a/src/vs/workbench/services/extensions/common/extensionHostManager.ts b/src/vs/workbench/services/extensions/common/extensionHostManager.ts index 265b64f6160..11f4c19ab49 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostManager.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostManager.ts @@ -19,7 +19,7 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { ManagedRemoteConnection, RemoteConnectionType, RemoteAuthorityResolverErrorCode, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService'; +import { IConnectCallback, IRemoteSocketFactoryService, ISocketFactory } from 'vs/platform/remote/common/remoteSocketFactoryService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -461,34 +461,39 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { } private registerManagedSocketFactory(messaging: ManagedRemoteConnection, proxy: IExtensionHostProxy) { - this._remoteSocketFactoryService.register(RemoteConnectionType.Managed, resolved => { - if (resolved.id !== messaging.id) { - return undefined; + const that = this; + this._remoteSocketFactoryService.register(RemoteConnectionType.Managed, new class implements ISocketFactory { + + supports(connectTo: ManagedRemoteConnection): boolean { + return (connectTo.id === messaging.id); } - return { - connect: ({ id: factoryId }, path, query, debugLabel, callback) => { - proxy.openRemoteSocket(factoryId).then(socketId => { - const half: RemoteSocketHalf = { - onClose: new Emitter(), - onData: new Emitter(), - onEnd: new Emitter(), - }; - this._remoteSockets.set(socketId, half); + connect(connectTo: ManagedRemoteConnection, path: string, query: string, debugLabel: string, callback: IConnectCallback): void { + if (connectTo.id !== messaging.id) { + return callback(new Error('Invalid connectTo'), undefined); + } - ManagedSocket.connect(socketId, proxy, path, query, debugLabel, half) - .then( - socket => { - socket.onDidDispose(() => this._remoteSockets.delete(socketId)); - callback(undefined, socket); - }, - err => { - this._remoteSockets.delete(socketId); - callback(err, undefined); - }); - }).catch(err => callback(err, undefined)); - }, - }; + const factoryId = connectTo.id; + 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, proxy, path, query, debugLabel, half) + .then( + socket => { + socket.onDidDispose(() => that._remoteSockets.delete(socketId)); + callback(undefined, socket); + }, + err => { + that._remoteSockets.delete(socketId); + callback(err, undefined); + }); + }).catch(err => callback(err, undefined)); + } }); } diff --git a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts index a79123f6cc5..70aa7236fb2 100644 --- a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts @@ -62,7 +62,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { constructor( public readonly runningLocation: RemoteRunningLocation, private readonly _initDataProvider: IRemoteExtensionHostDataProvider, - @IRemoteSocketFactoryService private readonly socketFactories: IRemoteSocketFactoryService, + @IRemoteSocketFactoryService private readonly remoteSocketFactoryService: IRemoteSocketFactoryService, @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @@ -85,26 +85,21 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { } public start(): Promise { + const options: IConnectionOptions = { + commit: this._productService.commit, + quality: this._productService.quality, + addressProvider: { + getAddress: async () => { + const { authority } = await this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority); + return { connectTo: authority.connectTo, connectionToken: authority.connectionToken }; + } + }, + remoteSocketFactoryService: this.remoteSocketFactoryService, + signService: this._signService, + logService: this._logService, + ipcLogger: null + }; return this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority).then((resolverResult) => { - const socketFactory = this.socketFactories.create(resolverResult.authority.connectTo); - if (!socketFactory) { - throw new Error('No socket factory found for remote authority'); - } - - const options: IConnectionOptions = { - commit: this._productService.commit, - quality: this._productService.quality, - socketFactory, - addressProvider: { - getAddress: async () => { - const { authority } = await this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority); - return { connectTo: authority.connectTo, connectionToken: authority.connectionToken }; - } - }, - signService: this._signService, - logService: this._logService, - ipcLogger: null - }; const startParams: IRemoteExtensionHostStartParams = { language: platform.language, diff --git a/src/vs/workbench/services/remote/browser/remoteAgentService.ts b/src/vs/workbench/services/remote/browser/remoteAgentService.ts index 33535d03085..0170a861289 100644 --- a/src/vs/workbench/services/remote/browser/remoteAgentService.ts +++ b/src/vs/workbench/services/remote/browser/remoteAgentService.ts @@ -23,7 +23,7 @@ import { IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSoc export class RemoteAgentService extends AbstractRemoteAgentService implements IRemoteAgentService { constructor( - @IRemoteSocketFactoryService socketFactories: IRemoteSocketFactoryService, + @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(socketFactories, userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService); + super(remoteSocketFactoryService, userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService); } } diff --git a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts index 62eae669bf2..9550c6a76d6 100644 --- a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts +++ b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts @@ -29,7 +29,7 @@ export abstract class AbstractRemoteAgentService extends Disposable implements I private _environment: Promise | null; constructor( - @IRemoteSocketFactoryService private readonly socketFactories: IRemoteSocketFactoryService, + @IRemoteSocketFactoryService private readonly remoteSocketFactoryService: IRemoteSocketFactoryService, @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, @IWorkbenchEnvironmentService protected readonly _environmentService: IWorkbenchEnvironmentService, @IProductService productService: IProductService, @@ -39,7 +39,7 @@ export abstract class AbstractRemoteAgentService extends Disposable implements I ) { super(); if (this._environmentService.remoteAuthority) { - this._connection = this._register(new RemoteAgentConnection(this._environmentService.remoteAuthority, productService.commit, productService.quality, this.socketFactories, 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; } @@ -149,7 +149,7 @@ class RemoteAgentConnection extends Disposable implements IRemoteAgentConnection remoteAuthority: string, private readonly _commit: string | undefined, private readonly _quality: string | undefined, - private readonly _socketFactories: IRemoteSocketFactoryService, + private readonly _remoteSocketFactoryService: IRemoteSocketFactoryService, private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, private readonly _signService: ISignService, private readonly _logService: ILogService @@ -192,35 +192,28 @@ class RemoteAgentConnection extends Disposable implements IRemoteAgentConnection private async _createConnection(): Promise> { let firstCall = true; + const options: IConnectionOptions = { + commit: this._commit, + quality: this._quality, + addressProvider: { + getAddress: async () => { + if (firstCall) { + firstCall = false; + } else { + this._onReconnecting.fire(undefined); + } + const { authority } = await this._remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority); + 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 + }; let connection: ManagementPersistentConnection; const start = Date.now(); try { - const { authority } = await this._remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority); - const socketFactory = this._socketFactories.create(authority.connectTo); - if (!socketFactory) { - throw new Error(`No socket factory found for ${authority}`); - } - - const options: IConnectionOptions = { - commit: this._commit, - quality: this._quality, - socketFactory, - addressProvider: { - getAddress: async () => { - if (firstCall) { - firstCall = false; - } else { - this._onReconnecting.fire(undefined); - } - const { authority } = await this._remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority); - return { connectTo: authority.connectTo, connectionToken: authority.connectionToken }; - } - }, - signService: this._signService, - logService: this._logService, - ipcLogger: false ? new IPCLogger(`Local \u2192 Remote`, `Remote \u2192 Local`) : null - }; - connection = this._register(await connectRemoteAgentManagement(options, this.remoteAuthority, `renderer`)); } finally { this._initialConnectionMs = Date.now() - start; diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index 45b26d67fd9..7ce33fccd67 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -621,10 +621,7 @@ export class TunnelModel extends Disposable { if (!existingTunnel) { const authority = this.environmentService.remoteAuthority; const addressProvider: IAddressProvider | undefined = authority ? { - getAddress: async () => { - const r = await this.remoteAuthorityResolverService.resolveAuthority(authority); - return { connectTo: r.authority.connectTo, connectionToken: r.authority.connectionToken }; - } + getAddress: async () => { return (await this.remoteAuthorityResolverService.resolveAuthority(authority)).authority; } } : undefined; const key = makeAddress(tunnelProperties.remote.host, tunnelProperties.remote.port); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 787b084d908..caa57e74bb6 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -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'; @@ -1939,10 +1938,6 @@ export class TestRemoteAgentService implements IRemoteAgentService { declare readonly _serviceBrand: undefined; - socketFactory: ISocketFactory = { - connect() { } - }; - getConnection(): IRemoteAgentConnection | null { return null; } async getEnvironment(): Promise { return null; } async getRawEnvironment(): Promise { return null; } From 1aa547712e4b132e7041e19a2de14e69cb762aa2 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 24 Apr 2023 15:48:28 +0200 Subject: [PATCH 05/16] Get rid of `IConnectCallback` --- .../remote/browser/browserSocketFactory.ts | 21 ++++---- .../remote/common/remoteAgentConnection.ts | 24 ++++----- .../common/remoteSocketFactoryService.ts | 12 ++--- .../platform/remote/node/nodeSocketFactory.ts | 39 +++++++------- .../extensions/common/extensionHostManager.ts | 52 ++++++++++--------- 5 files changed, 72 insertions(+), 76 deletions(-) diff --git a/src/vs/platform/remote/browser/browserSocketFactory.ts b/src/vs/platform/remote/browser/browserSocketFactory.ts index c3b240af03d..765b2f3fa6f 100644 --- a/src/vs/platform/remote/browser/browserSocketFactory.ts +++ b/src/vs/platform/remote/browser/browserSocketFactory.ts @@ -9,7 +9,7 @@ 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/remoteSocketFactoryService'; +import { ISocketFactory } from 'vs/platform/remote/common/remoteSocketFactoryService'; import { RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode, RemoteConnectionType, WebSocketRemoteConnection } from 'vs/platform/remote/common/remoteAuthorityResolver'; export interface IWebSocketFactory { @@ -277,16 +277,15 @@ export class BrowserSocketFactory implements ISocketFactory callback(err, undefined)); - socket.onOpen(() => { - errorListener.dispose(); - callback(undefined, new BrowserSocket(socket, debugLabel)); + connect({ host, port }: WebSocketRemoteConnection, path: string, query: string, debugLabel: string): Promise { + return new Promise((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)); + }); }); } } - - - diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts index 7c66a0976e6..6b24b148aa8 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -189,26 +189,24 @@ function createSocket(logService: ILogService, remot const sw = StopWatch.create(false); logService.info(`Creating a socket (${debugLabel})...`); performance.mark(`code/willCreateSocket/${debugConnectionType}`); - remoteSocketFactoryService.connect(connectTo, 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; } diff --git a/src/vs/platform/remote/common/remoteSocketFactoryService.ts b/src/vs/platform/remote/common/remoteSocketFactoryService.ts index b6d129a530a..78160b31938 100644 --- a/src/vs/platform/remote/common/remoteSocketFactoryService.ts +++ b/src/vs/platform/remote/common/remoteSocketFactoryService.ts @@ -20,16 +20,12 @@ export interface IRemoteSocketFactoryService { */ register(type: T, factory: ISocketFactory): void; - connect(connectTo: RemoteConnection, path: string, query: string, debugLabel: string, callback: IConnectCallback): void; + connect(connectTo: RemoteConnection, path: string, query: string, debugLabel: string): Promise; } export interface ISocketFactory { supports(connectTo: RemoteConnectionOfType): boolean; - connect(connectTo: RemoteConnectionOfType, path: string, query: string, debugLabel: string, callback: IConnectCallback): void; -} - -export interface IConnectCallback { - (err: any | undefined, socket: ISocket | undefined): void; + connect(connectTo: RemoteConnectionOfType, path: string, query: string, debugLabel: string): Promise; } export class RemoteSocketFactoryService implements IRemoteSocketFactoryService { @@ -47,11 +43,11 @@ export class RemoteSocketFactoryService implements IRemoteSocketFactoryService { return factories.find(factory => factory.supports(messagePassing)); } - public connect(connectTo: RemoteConnection, path: string, query: string, debugLabel: string, callback: IConnectCallback): void { + public connect(connectTo: RemoteConnection, path: string, query: string, debugLabel: string): Promise { const socketFactory = this.getSocketFactory(connectTo); if (!socketFactory) { throw new Error(`No socket factory found for ${connectTo}`); } - return socketFactory.connect(connectTo, path, query, debugLabel, callback); + return socketFactory.connect(connectTo, path, query, debugLabel); } } diff --git a/src/vs/platform/remote/node/nodeSocketFactory.ts b/src/vs/platform/remote/node/nodeSocketFactory.ts index 8a2776b5d33..4641791946a 100644 --- a/src/vs/platform/remote/node/nodeSocketFactory.ts +++ b/src/vs/platform/remote/node/nodeSocketFactory.ts @@ -4,10 +4,11 @@ *--------------------------------------------------------------------------------------------*/ 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 { makeRawSocketHeaders } from 'vs/platform/remote/common/managedSocket'; import { RemoteConnectionType, WebSocketRemoteConnection } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { IConnectCallback, ISocketFactory } from 'vs/platform/remote/common/remoteSocketFactoryService'; +import { ISocketFactory } from 'vs/platform/remote/common/remoteSocketFactoryService'; export const nodeSocketFactory = new class implements ISocketFactory { @@ -15,26 +16,26 @@ export const nodeSocketFactory = new class implements ISocketFactory callback(err, undefined); + connect({ host, port }: WebSocketRemoteConnection, path: string, query: string, debugLabel: string): Promise { + return new Promise((resolve, reject) => { + const socket = net.createConnection({ host: host, port: port }, () => { + socket.removeListener('error', reject); - const socket = net.createConnection({ host: host, port: port }, () => { - socket.removeListener('error', errorListener); + socket.write(makeRawSocketHeaders(path, query, debugLabel)); - 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); } }; diff --git a/src/vs/workbench/services/extensions/common/extensionHostManager.ts b/src/vs/workbench/services/extensions/common/extensionHostManager.ts index 11f4c19ab49..401e5d3e2ec 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostManager.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostManager.ts @@ -11,7 +11,7 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { StopWatch } from 'vs/base/common/stopwatch'; import { URI } from 'vs/base/common/uri'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; -import { SocketCloseEvent, SocketCloseEventType } from 'vs/base/parts/ipc/common/ipc.net'; +import { ISocket, SocketCloseEvent, SocketCloseEventType } from 'vs/base/parts/ipc/common/ipc.net'; import * as nls from 'vs/nls'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; @@ -19,7 +19,7 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { ManagedRemoteConnection, RemoteConnectionType, RemoteAuthorityResolverErrorCode, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { IConnectCallback, IRemoteSocketFactoryService, ISocketFactory } from 'vs/platform/remote/common/remoteSocketFactoryService'; +import { IRemoteSocketFactoryService, ISocketFactory } from 'vs/platform/remote/common/remoteSocketFactoryService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -468,31 +468,33 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { return (connectTo.id === messaging.id); } - connect(connectTo: ManagedRemoteConnection, path: string, query: string, debugLabel: string, callback: IConnectCallback): void { - if (connectTo.id !== messaging.id) { - return callback(new Error('Invalid connectTo'), undefined); - } + connect(connectTo: ManagedRemoteConnection, path: string, query: string, debugLabel: string): Promise { + return new Promise((resolve, reject) => { + if (connectTo.id !== messaging.id) { + return reject(new Error('Invalid connectTo')); + } - const factoryId = connectTo.id; - proxy.openRemoteSocket(factoryId).then(socketId => { - const half: RemoteSocketHalf = { - onClose: new Emitter(), - onData: new Emitter(), - onEnd: new Emitter(), - }; - that._remoteSockets.set(socketId, half); + const factoryId = connectTo.id; + 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, proxy, path, query, debugLabel, half) - .then( - socket => { - socket.onDidDispose(() => that._remoteSockets.delete(socketId)); - callback(undefined, socket); - }, - err => { - that._remoteSockets.delete(socketId); - callback(err, undefined); - }); - }).catch(err => callback(err, undefined)); + ManagedSocket.connect(socketId, proxy, path, query, debugLabel, half) + .then( + socket => { + socket.onDidDispose(() => that._remoteSockets.delete(socketId)); + resolve(socket); + }, + err => { + that._remoteSockets.delete(socketId); + reject(err); + }); + }).catch(reject); + }); } }); } From e71c4295d3eeb475c1dcffc3a338fee093ec7328 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 9 May 2023 12:16:40 -0700 Subject: [PATCH 06/16] address pr comment --- .../remoteAuthorityResolverService.ts | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/remote/electron-sandbox/remoteAuthorityResolverService.ts b/src/vs/platform/remote/electron-sandbox/remoteAuthorityResolverService.ts index 744b5970583..701257da09b 100644 --- a/src/vs/platform/remote/electron-sandbox/remoteAuthorityResolverService.ts +++ b/src/vs/platform/remote/electron-sandbox/remoteAuthorityResolverService.ts @@ -22,14 +22,14 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot private readonly _resolveAuthorityRequests: Map>; private readonly _connectionTokens: Map; - private readonly _canonicalURIRequests: Map>; + private readonly _canonicalURIRequests: Map }>; private _canonicalURIProvider: ((uri: URI) => Promise) | null; constructor(@IProductService productService: IProductService) { super(); this._resolveAuthorityRequests = new Map>(); this._connectionTokens = new Map(); - this._canonicalURIRequests = new Map>(); + this._canonicalURIRequests = new Map(); this._canonicalURIProvider = null; RemoteAuthorities.setServerRootPath(getRemoteServerRootPath(productService)); @@ -44,12 +44,15 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot async getCanonicalURI(uri: URI): Promise { const key = uri.toString(); - if (!this._canonicalURIRequests.has(key)) { - const request = new DeferredPromise(); - this._canonicalURIProvider?.(uri).then((uri) => request.complete(uri), (err) => request.error(err)); - this._canonicalURIRequests.set(key, request); + const existing = this._canonicalURIRequests.get(key); + if (existing) { + return existing.result.p; } - return this._canonicalURIRequests.get(key)!.p; + + const result = new DeferredPromise(); + 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 { @@ -105,8 +108,8 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot _setCanonicalURIProvider(provider: (uri: URI) => Promise): void { this._canonicalURIProvider = provider; - this._canonicalURIRequests.forEach((value, key) => { - this._canonicalURIProvider!(URI.parse(key)).then((uri) => value.complete(uri), (err) => value.error(err)); + this._canonicalURIRequests.forEach(({ result, input }) => { + this._canonicalURIProvider!(input).then((uri) => result.complete(uri), (err) => result.error(err)); }); } } From 6dcaf9a3c698aa59dcf218a41ca61c6a7f1e893e Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 10 May 2023 15:42:15 +0200 Subject: [PATCH 07/16] :lipstick: reduce diff --- src/vs/workbench/electron-sandbox/window.ts | 3 +-- .../services/remote/electron-sandbox/remoteAgentService.ts | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index f13fbdb47f6..7accc1db263 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -842,8 +842,7 @@ export class NativeWindow extends Disposable { const remoteAuthority = this.environmentService.remoteAuthority; const addressProvider: IAddressProvider | undefined = remoteAuthority ? { getAddress: async (): Promise => { - const { authority } = await this.remoteAuthorityResolverService.resolveAuthority(remoteAuthority); - return { connectTo: authority.connectTo, connectionToken: authority.connectionToken }; + return (await this.remoteAuthorityResolverService.resolveAuthority(remoteAuthority)).authority; } } : undefined; let tunnel = await this.tunnelService.getExistingTunnel(portMappingRequest.address, portMappingRequest.port); diff --git a/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts b/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts index 429e9dc926e..550b988c939 100644 --- a/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts +++ b/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts @@ -24,7 +24,7 @@ import { IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSoc export class RemoteAgentService extends AbstractRemoteAgentService implements IRemoteAgentService { constructor( - @IRemoteSocketFactoryService socketFactoryCollection: IRemoteSocketFactoryService, + @IRemoteSocketFactoryService remoteSocketFactoryService: IRemoteSocketFactoryService, @IUserDataProfileService userDataProfileService: IUserDataProfileService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IProductService productService: IProductService, @@ -32,7 +32,7 @@ export class RemoteAgentService extends AbstractRemoteAgentService implements IR @ISignService signService: ISignService, @ILogService logService: ILogService, ) { - super(socketFactoryCollection, userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService); + super(remoteSocketFactoryService, userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService); } } From 0029cab57816db17c2f8b5549efacb0c018701e7 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 10 May 2023 23:56:44 +0200 Subject: [PATCH 08/16] :lipstick: reduce diff --- .../services/extensions/common/abstractExtensionService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index 6f149f0309e..cd829cd14f5 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -966,7 +966,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx }, _onExtensionRuntimeError: (extensionId: ExtensionIdentifier, err: Error): void => { return this._onExtensionRuntimeError(extensionId, err); - }, + } }; } From 1439f53b9c943b7ae26eb8d44f6df6c06d04a6fc Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 11 May 2023 00:03:00 +0200 Subject: [PATCH 09/16] :lipstick: improve logging, make sure to continue throwing if the remote authority doesn't contain a '+' and is not in a 'hostname:port' format --- .../nativeExtensionService.ts | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts b/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts index bc98c67587c..b1499660215 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts @@ -277,22 +277,18 @@ export class NativeExtensionService extends AbstractExtensionService implements const authorityPlusIndex = remoteAuthority.indexOf('+'); if (authorityPlusIndex === -1) { // This authority does not need to be resolved, simply parse the port number - try { - const { host, port } = parseAuthorityWithPort(remoteAuthority); - return { - authority: { - authority: remoteAuthority, - connectTo: { - type: RemoteConnectionType.WebSocket, - host, - port - }, - connectionToken: undefined - } - }; - } catch { - // continue - } + const { host, port } = parseAuthorityWithPort(remoteAuthority); + return { + authority: { + authority: remoteAuthority, + connectTo: { + type: RemoteConnectionType.WebSocket, + host, + port + }, + connectionToken: undefined + } + }; } const localProcessExtensionHosts = this._getExtensionHostManagers(ExtensionHostKind.LocalProcess); @@ -398,7 +394,7 @@ export class NativeExtensionService extends AbstractExtensionService implements performance.mark(`code/willResolveAuthority/${authorityPrefix}`); const result = await this._resolveAuthority(remoteAuthority); performance.mark(`code/didResolveAuthorityOK/${authorityPrefix}`); - this._logService.info(`resolveAuthority(${authorityPrefix}) returned '${result.authority}' after ${sw.elapsed()} ms`); + this._logService.info(`resolveAuthority(${authorityPrefix}) returned '${result.authority.connectTo}' after ${sw.elapsed()} ms`); return result; } catch (err) { performance.mark(`code/didResolveAuthorityError/${authorityPrefix}`); From dde5a4f35649c92c2a33e898baca1e24dbc06990 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 11 May 2023 11:24:24 +0200 Subject: [PATCH 10/16] Add dedicated protocol for managed sockets (mainThreadManagedSockets and extHostManagedSockets) --- .../common/remoteSocketFactoryService.ts | 11 +- .../api/browser/extensionHost.contribution.ts | 1 + .../api/browser/mainThreadExtensionService.ts | 65 +++---- .../api/browser/mainThreadManagedSockets.ts | 178 ++++++++++++++++++ .../workbench/api/common/extHost.api.impl.ts | 3 + .../api/common/extHost.common.services.ts | 2 + .../workbench/api/common/extHost.protocol.ts | 28 ++- .../api/common/extHostExtensionService.ts | 72 ++----- .../api/common/extHostManagedSockets.ts | 106 +++++++++++ .../extensions/browser/extensionService.ts | 2 +- .../extensions/common/extHostCustomers.ts | 8 - .../extensions/common/extensionHostManager.ts | 75 +------- .../extensions/common/extensionHostProxy.ts | 5 - .../services/remote/common/managedSocket.ts | 91 --------- 14 files changed, 366 insertions(+), 281 deletions(-) create mode 100644 src/vs/workbench/api/browser/mainThreadManagedSockets.ts create mode 100644 src/vs/workbench/api/common/extHostManagedSockets.ts delete mode 100644 src/vs/workbench/services/remote/common/managedSocket.ts diff --git a/src/vs/platform/remote/common/remoteSocketFactoryService.ts b/src/vs/platform/remote/common/remoteSocketFactoryService.ts index 78160b31938..6d6eacfc854 100644 --- a/src/vs/platform/remote/common/remoteSocketFactoryService.ts +++ b/src/vs/platform/remote/common/remoteSocketFactoryService.ts @@ -3,6 +3,7 @@ * 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'; @@ -18,7 +19,7 @@ export interface IRemoteSocketFactoryService { * @param factory function that returns the socket factory, or undefined if * it can't handle the data. */ - register(type: T, factory: ISocketFactory): void; + register(type: T, factory: ISocketFactory): IDisposable; connect(connectTo: RemoteConnection, path: string, query: string, debugLabel: string): Promise; } @@ -33,9 +34,15 @@ export class RemoteSocketFactoryService implements IRemoteSocketFactoryService { private readonly factories: { [T in RemoteConnectionType]?: ISocketFactory[] } = {}; - public register(type: T, factory: ISocketFactory): void { + public register(type: T, factory: ISocketFactory): 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(messagePassing: RemoteConnectionOfType): ISocketFactory | undefined { diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index 65aeecacbf5..cc5768ce225 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -43,6 +43,7 @@ import './mainThreadLanguageFeatures'; import './mainThreadLanguages'; import './mainThreadLogService'; import './mainThreadMessageService'; +import './mainThreadManagedSockets'; import './mainThreadOutputService'; import './mainThreadProgress'; import './mainThreadQuickDiff'; diff --git a/src/vs/workbench/api/browser/mainThreadExtensionService.ts b/src/vs/workbench/api/browser/mainThreadExtensionService.ts index d06644993d4..cbddc0c233b 100644 --- a/src/vs/workbench/api/browser/mainThreadExtensionService.ts +++ b/src/vs/workbench/api/browser/mainThreadExtensionService.ts @@ -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, RemoteConnectionType } 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'; @@ -25,7 +25,8 @@ import { ExtensionHostKind } from 'vs/workbench/services/extensions/common/exten import { IExtensionDescriptionDelta } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; 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, IManagedSocketCallbacks } from 'vs/workbench/services/extensions/common/extHostCustomers'; +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'; @@ -34,7 +35,6 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha private readonly _extensionHostKind: ExtensionHostKind; private readonly _internalExtensionService: IInternalExtensionService; - private readonly _managedSocketCallbacks: IManagedSocketCallbacks; constructor( extHostContext: IExtHostContext, @@ -51,7 +51,6 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha const internalExtHostContext = (extHostContext); this._internalExtensionService = internalExtHostContext.internalExtensionService; - this._managedSocketCallbacks = internalExtHostContext.managedSocketCallbacks; internalExtHostContext._setExtensionHostProxy( new ExtensionHostProxy(extHostContext.getProxy(ExtHostContext.ExtHostExtensionService)) ); @@ -61,18 +60,6 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha public dispose(): void { } - $onDidRemoteSocketHaveData(id: number, data: VSBuffer): void { - this._managedSocketCallbacks.onDidRemoteSocketHaveData(id, data); - } - - $onDidRemoteSocketClose(id: number, error: string | undefined): void { - this._managedSocketCallbacks.onDidRemoteSocketClose(id, error ? new Error(error) : undefined); - } - - $onDidRemoteSocketEnd(id: number): void { - this._managedSocketCallbacks.onDidRemoteSocketEnd(id); - } - $getExtension(extensionId: string) { return this._extensionService.getExtension(extensionId); } @@ -214,13 +201,7 @@ class ExtensionHostProxy implements IExtensionHostProxy { ) { } async resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise { - const resolved = await this._actual.$resolveAuthority(remoteAuthority, resolveAttempt); - if (resolved.type === 'ok') { - resolved.value.authority.toString = function () { - return this.connectTo.type === RemoteConnectionType.Managed ? `ManagedSocket#${this.connectTo.id}` : `${this.connectTo.host}:${this.connectTo.type}`; - }; - } - + const resolved = reviveResolveAuthorityResult(await this._actual.$resolveAuthority(remoteAuthority, resolveAttempt)); return resolved; } async getCanonicalURI(remoteAuthority: string, uri: URI): Promise { @@ -257,16 +238,32 @@ class ExtensionHostProxy implements IExtensionHostProxy { test_down(size: number): Promise { return this._actual.$test_down(size); } - openRemoteSocket(factoryId: number): Promise { - return this._actual.$openRemoteSocket(factoryId); - } - remoteSocketWrite(socketId: number, buffer: VSBuffer): void { - return this._actual.$remoteSocketWrite(socketId, buffer); - } - remoteSocketEnd(socketId: number): void { - return this._actual.$remoteSocketEnd(socketId); - } - remoteSocketDrain(socketId: number): Promise { - return this._actual.$remoteSocketDrain(socketId); +} + +function reviveResolveAuthorityResult(result: Dto): IResolveAuthorityResult { + if (result.type === 'ok') { + return { + type: 'ok', + value: { + ...result.value, + authority: reviveResolvedAuthority(result.value.authority), + } + }; + } else { + return result; } } + +function reviveResolvedAuthority(resolvedAuthority: Dto): ResolvedAuthority { + return { + ...resolvedAuthority, + connectTo: reviveConnection(resolvedAuthority.connectTo), + }; +} + +function reviveConnection(connection: Dto): RemoteConnection { + if (connection.type === RemoteConnectionType.WebSocket) { + return new WebSocketRemoteConnection(connection.host, connection.port); + } + return new ManagedRemoteConnection(connection.id); +} diff --git a/src/vs/workbench/api/browser/mainThreadManagedSockets.ts b/src/vs/workbench/api/browser/mainThreadManagedSockets.ts new file mode 100644 index 00000000000..ef532391c60 --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadManagedSockets.ts @@ -0,0 +1,178 @@ +/*--------------------------------------------------------------------------------------------- + * 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 } 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(); + private readonly _remoteSockets = new Map(); + + constructor( + extHostContext: IExtHostContext, + @IRemoteSocketFactoryService private readonly _remoteSocketFactoryService: IRemoteSocketFactoryService, + ) { + super(); + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostManagedSockets); + } + + async $registerSocketFactory(socketFactoryId: number): Promise { + const that = this; + const scoketFactory = new class implements ISocketFactory { + + supports(connectTo: ManagedRemoteConnection): boolean { + return (connectTo.id === socketFactoryId); + } + + connect(connectTo: ManagedRemoteConnection, path: string, query: string, debugLabel: string): Promise { + return new Promise((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, scoketFactory)); + + } + + async $unregisterSocketFactory(socketFactoryId: number): Promise { + 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(); + this._remoteSockets.delete(socketId); + } +} + +interface RemoteSocketHalf { + onData: Emitter; + onClose: Emitter; + onEnd: Emitter; +} + +export class ManagedSocket extends Disposable implements ISocket { + public static connect( + socketId: number, + proxy: ExtHostManagedSocketsShape, + path: string, query: string, debugLabel: string, + + half: { + onClose: Emitter; + onData: Emitter; + onEnd: Emitter; + } + ): Promise { + 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((resolve, reject) => { + d.add(socket.onData(d => { + if (d.indexOf(socketRawEndHeaderSequence) !== -1) { + resolve(socket); + } + })); + + d.add(socket.onClose(err => reject(err ?? new Error('socket closed')))); + d.add(socket.onEnd(() => reject(new Error('socket ended')))); + }).finally(() => d.dispose()); + } + + public onData: Event; + public onClose: Event; + public onEnd: Event; + + private readonly didDisposeEmitter = this._register(new Emitter()); + public onDidDispose = this.didDisposeEmitter.event; + + private ended = false; + + private constructor( + private readonly socketId: number, + private readonly proxy: ExtHostManagedSocketsShape, + private readonly debugLabel: string, + onCloseEmitter: Emitter, + onDataEmitter: Emitter, + onEndEmitter: Emitter, + ) { + super(); + this.onClose = this._register(onCloseEmitter).event; + this.onData = this._register(onDataEmitter).event; + this.onEnd = this._register(onEndEmitter).event; + } + + write(buffer: VSBuffer): void { + this.proxy.$remoteSocketWrite(this.socketId, buffer); + } + + end(): void { + this.ended = true; + this.proxy.$remoteSocketEnd(this.socketId); + } + + drain(): Promise { + 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(); + } +} diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 22f6ed8d346..3222ad9483f 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -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)); diff --git a/src/vs/workbench/api/common/extHost.common.services.ts b/src/vs/workbench/api/common/extHost.common.services.ts index d451acc438f..faf45b596a7 100644 --- a/src/vs/workbench/api/common/extHost.common.services.ts +++ b/src/vs/workbench/api/common/extHost.common.services.ts @@ -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); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index a6eaf0bf1a7..09f8c4677bf 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -952,6 +952,21 @@ export interface ExtHostWebviewViewsShape { $disposeWebviewView(webviewHandle: WebviewHandle): void; } +export interface MainThreadManagedSocketsShape extends IDisposable { + $registerSocketFactory(socketFactoryId: number): Promise; + $unregisterSocketFactory(socketFactoryId: number): Promise; + $onDidManagedSocketHaveData(socketId: number, data: VSBuffer): void; + $onDidManagedSocketClose(socketId: number, error: string | undefined): void; + $onDidManagedSocketEnd(socketId: number): void; +} + +export interface ExtHostManagedSocketsShape { + $openRemoteSocket(socketFactoryId: number): Promise; + $remoteSocketWrite(socketId: number, buffer: VSBuffer): void; + $remoteSocketEnd(socketId: number): void; + $remoteSocketDrain(socketId: number): Promise; +} + export enum CellOutputKind { Text = 1, Error = 2, @@ -1246,10 +1261,6 @@ export interface MainThreadExtensionServiceShape extends IDisposable { $onExtensionRuntimeError(extensionId: ExtensionIdentifier, error: SerializedError): void; $setPerformanceMarks(marks: performance.PerformanceMark[]): Promise; $asBrowserUri(uri: UriComponents): Promise; - - $onDidRemoteSocketHaveData(id: number, data: VSBuffer): void; - $onDidRemoteSocketClose(id: number, error: string | undefined): void; - $onDidRemoteSocketEnd(id: number): void; } export interface SCMProviderFeatures { @@ -1589,7 +1600,7 @@ export interface ExtHostSearchShape { } export interface ExtHostExtensionServiceShape { - $resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise; + $resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise>; /** * Returns `null` if no resolver for `remoteAuthority` is found. */ @@ -1606,11 +1617,6 @@ export interface ExtHostExtensionServiceShape { $test_latency(n: number): Promise; $test_up(b: VSBuffer): Promise; $test_down(size: number): Promise; - - $openRemoteSocket(factoryId: number): Promise; - $remoteSocketWrite(socketId: number, buffer: VSBuffer): void; - $remoteSocketEnd(socketId: number): void; - $remoteSocketDrain(socketId: number): Promise; } export interface FileSystemEvents { @@ -2512,6 +2518,7 @@ export const MainContext = { MainThreadInteractiveEditor: createProxyIdentifier('MainThreadInteractiveEditor'), MainThreadTheming: createProxyIdentifier('MainThreadTheming'), MainThreadTunnelService: createProxyIdentifier('MainThreadTunnelService'), + MainThreadManagedSockets: createProxyIdentifier('MainThreadManagedSockets'), MainThreadTimeline: createProxyIdentifier('MainThreadTimeline'), MainThreadTesting: createProxyIdentifier('MainThreadTesting'), MainThreadLocalization: createProxyIdentifier('MainThreadLocalizationShape'), @@ -2573,6 +2580,7 @@ export const ExtHostContext = { ExtHostSemanticSimilarity: createProxyIdentifier('ExtHostSemanticSimilarity'), ExtHostTheming: createProxyIdentifier('ExtHostTheming'), ExtHostTunnelService: createProxyIdentifier('ExtHostTunnelService'), + ExtHostManagedSockets: createProxyIdentifier('ExtHostManagedSockets'), ExtHostAuthentication: createProxyIdentifier('ExtHostAuthentication'), ExtHostTimeline: createProxyIdentifier('ExtHostTimeline'), ExtHostTesting: createProxyIdentifier('ExtHostTesting'), diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index 16548fdb328..75db96a50ca 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -8,7 +8,7 @@ import * as path from 'vs/base/common/path'; import * as performance from 'vs/base/common/performance'; import { originalFSPath, joinPath, extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; import { asPromise, Barrier, IntervalTimer, timeout } from 'vs/base/common/async'; -import { dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { dispose, toDisposable, Disposable } from 'vs/base/common/lifecycle'; import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; @@ -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` */ @@ -79,8 +81,6 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme readonly _serviceBrand: undefined; - private static remoteSocketIdCounter = 0; - abstract readonly extensionRuntime: ExtensionRuntime; private readonly _onDidChangeRemoteConnectionData = this._register(new Emitter()); @@ -120,11 +120,6 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme private _started: boolean; private _isTerminating: boolean = false; private _remoteConnectionData: IRemoteConnectionData | null; - private readonly _managedSocketFactories: Map Thenable>; - private readonly _managedRemoteSockets: Map; constructor( @IInstantiationService instaService: IInstantiationService, @@ -137,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; @@ -198,8 +194,6 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme this._resolvers = Object.create(null); this._started = false; this._remoteConnectionData = this._initData.remote.connectionData; - this._managedSocketFactories = new Map(); - this._managedRemoteSockets = new Map(); } public getRemoteConnectionData(): IRemoteConnectionData | null { @@ -800,7 +794,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme return { authorityPrefix, resolver: this._resolvers[authorityPrefix] }; } - public async $resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise { + public async $resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise> { const sw = StopWatch.create(false); const prefix = () => `[resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)},${resolveAttempt})][${sw.elapsed()}ms] `; const logInfo = (msg: string) => this._logService.info(`${prefix()}${msg}`); @@ -854,11 +848,16 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme connectionToken: result.connectionToken }; } else { - const factoryId = AbstractExtHostExtensionService.remoteSocketIdCounter++; - this._managedSocketFactories.set(factoryId, result.makeConnection); + // 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(factoryId), + connectTo: new ManagedRemoteConnection(socketFactoryId), connectionToken: result.connectionToken }; } @@ -866,7 +865,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme return { type: 'ok', value: { - authority, + authority: authority as Dto, options, tunnelInformation, } @@ -889,47 +888,6 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme } } - public async $openRemoteSocket(factoryId: number): Promise { - const factory = this._managedSocketFactories.get(factoryId); - if (!factory) { - throw new Error(`No socket factory with id ${factoryId}`); - } - - const id = AbstractExtHostExtensionService.remoteSocketIdCounter++; - const socket = await factory(); - const disposable = new DisposableStore(); - - this._managedRemoteSockets.set(id, { object: socket, disposer: disposable }); - disposable.add(toDisposable(() => this._managedRemoteSockets.delete(id))); - disposable.add(socket.onDidEnd(() => { - this._mainThreadExtensionsProxy.$onDidRemoteSocketEnd(id); - disposable.dispose(); - })); - disposable.add(socket.onDidClose(e => { - this._mainThreadExtensionsProxy.$onDidRemoteSocketClose(id, e?.stack ?? e?.message); - disposable.dispose(); - })); - disposable.add(socket.onDidReceiveMessage(e => this._mainThreadExtensionsProxy.$onDidRemoteSocketHaveData(id, VSBuffer.wrap(e)))); - - return id; - } - - public $remoteSocketDrain(id: number): Promise { - return this._managedRemoteSockets.get(id)?.object.drainHandler?.() ?? Promise.resolve(); - } - - public $remoteSocketEnd(id: number): void { - const socket = this._managedRemoteSockets.get(id); - if (socket) { - socket.object.endHandler(); - socket.disposer.dispose(); - } - } - - public $remoteSocketWrite(id: number, buffer: VSBuffer): void { - this._managedRemoteSockets.get(id)?.object.dataHandler(buffer.buffer); - } - public async $getCanonicalURI(remoteAuthority: string, uriComponents: UriComponents): Promise { this._logService.info(`$getCanonicalURI invoked for authority (${getRemoteAuthorityPrefix(remoteAuthority)})`); diff --git a/src/vs/workbench/api/common/extHostManagedSockets.ts b/src/vs/workbench/api/common/extHostManagedSockets.ts new file mode 100644 index 00000000000..ccf2c7be634 --- /dev/null +++ b/src/vs/workbench/api/common/extHostManagedSockets.ts @@ -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): void; + readonly _serviceBrand: undefined; +} + +export const IExtHostManagedSockets = createDecorator('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 = new Map(); + + constructor( + @IExtHostRpcService extHostRpc: IExtHostRpcService, + ) { + this._proxy = extHostRpc.getProxy(MainContext.MainThreadManagedSockets); + } + + setFactory(socketFactoryId: number, makeConnection: () => Thenable): 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 { + 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.dataHandler(buffer.buffer); + } + + $remoteSocketEnd(socketId: number): void { + const socket = this._managedRemoteSockets.get(socketId); + if (socket) { + socket.actual.endHandler(); + socket.dispose(); + } + } + + $remoteSocketDrain(socketId: number): Promise { + return this._managedRemoteSockets.get(socketId)?.actual.drainHandler?.() ?? Promise.resolve(); + } +} + +class ManagedSocketFactory { + constructor( + public readonly socketFactoryId: number, + public readonly makeConnection: () => Thenable, + ) { } +} + +class ManagedSocket extends Disposable { + constructor( + public readonly socketId: number, + public readonly actual: vscode.ManagedMessagePassing, + disposer: DisposableStore, + ) { + super(); + this._register(disposer); + } +} diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index b5ff879733a..9d789d8f995 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -255,7 +255,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten performance.mark(`code/willResolveAuthority/${authorityPrefix}`); const result = await this._resolveAuthority(remoteAuthority); performance.mark(`code/didResolveAuthorityOK/${authorityPrefix}`); - this._logService.info(`resolveAuthority(${authorityPrefix}) returned '${result.authority}' after ${sw.elapsed()} ms`); + this._logService.info(`resolveAuthority(${authorityPrefix}) returned '${result.authority.connectTo}' after ${sw.elapsed()} ms`); return result; } catch (err) { performance.mark(`code/didResolveAuthorityError/${authorityPrefix}`); diff --git a/src/vs/workbench/services/extensions/common/extHostCustomers.ts b/src/vs/workbench/services/extensions/common/extHostCustomers.ts index 34123e72b15..0d3104fcded 100644 --- a/src/vs/workbench/services/extensions/common/extHostCustomers.ts +++ b/src/vs/workbench/services/extensions/common/extHostCustomers.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { VSBuffer } from 'vs/base/common/buffer'; import { IDisposable } from 'vs/base/common/lifecycle'; import { BrandedService, IConstructorSignature } from 'vs/platform/instantiation/common/instantiation'; import { ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensionHostKind'; @@ -16,15 +15,8 @@ export interface IExtHostContext extends IRPCProtocol { readonly extensionHostKind: ExtensionHostKind; } -export interface IManagedSocketCallbacks { - onDidRemoteSocketHaveData(id: number, data: VSBuffer): void; - onDidRemoteSocketEnd(id: number): void; - onDidRemoteSocketClose(id: number, error: Error | undefined): void; -} - export interface IInternalExtHostContext extends IExtHostContext { readonly internalExtensionService: IInternalExtensionService; - readonly managedSocketCallbacks: IManagedSocketCallbacks; _setExtensionHostProxy(extensionHostProxy: IExtensionHostProxy): void; _setAllMainProxyIdentifiers(mainProxyIdentifiers: ProxyIdentifier[]): void; } diff --git a/src/vs/workbench/services/extensions/common/extensionHostManager.ts b/src/vs/workbench/services/extensions/common/extensionHostManager.ts index 401e5d3e2ec..d4db167e5e5 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostManager.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostManager.ts @@ -11,15 +11,13 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { StopWatch } from 'vs/base/common/stopwatch'; import { URI } from 'vs/base/common/uri'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; -import { ISocket, SocketCloseEvent, SocketCloseEventType } from 'vs/base/parts/ipc/common/ipc.net'; import * as nls from 'vs/nls'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { ManagedRemoteConnection, RemoteConnectionType, RemoteAuthorityResolverErrorCode, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { IRemoteSocketFactoryService, ISocketFactory } from 'vs/platform/remote/common/remoteSocketFactoryService'; +import { RemoteAuthorityResolverErrorCode, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -31,7 +29,6 @@ import { ExtensionRunningLocation } from 'vs/workbench/services/extensions/commo import { ActivationKind, ExtensionActivationReason, ExtensionHostExtensions, ExtensionHostStartup, IExtensionHost, IInternalExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { Proxied, ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import { IRPCProtocolLogger, RPCProtocol, RequestInitiator, ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol'; -import { ManagedSocket } from 'vs/workbench/services/remote/common/managedSocket'; // Enable to see detailed message communication between window and extension host const LOG_EXTENSION_HOST_COMMUNICATION = false; @@ -88,13 +85,6 @@ type ExtensionHostStartupEvent = { errorStack?: string; }; - -interface RemoteSocketHalf { - onData: Emitter; - onClose: Emitter; - onEnd: Emitter; -} - class ExtensionHostManager extends Disposable implements IExtensionHostManager { public readonly onDidExit: Event<[number, string | null]>; @@ -112,7 +102,6 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { private readonly _extensionHost: IExtensionHost; private _proxy: Promise | null; private _hasStarted = false; - private readonly _remoteSockets = new Map(); public get kind(): ExtensionHostKind { return this._extensionHost.runningLocation.kind; @@ -126,7 +115,6 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { extensionHost: IExtensionHost, initialActivationEvents: string[], private readonly _internalExtensionService: IInternalExtensionService, - @IRemoteSocketFactoryService private readonly _remoteSocketFactoryService: IRemoteSocketFactoryService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @@ -299,23 +287,6 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { //#region internal internalExtensionService: this._internalExtensionService, - managedSocketCallbacks: { - onDidRemoteSocketHaveData: (id, data) => { - this._remoteSockets.get(id)?.onData.fire(data); - }, - onDidRemoteSocketEnd: id => { - this._remoteSockets.get(id)?.onEnd.fire(); - this._remoteSockets.delete(id); - }, - onDidRemoteSocketClose: (id, error) => { - this._remoteSockets.get(id)?.onClose.fire({ - type: SocketCloseEventType.NodeSocketCloseEvent, - error, - hadError: !!error - }); - this._remoteSockets.delete(id); - }, - }, _setExtensionHostProxy: (value: IExtensionHostProxy): void => { extensionHostProxy = value; }, @@ -438,10 +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}`); - if (resolverResult.value.authority.connectTo.type === RemoteConnectionType.Managed) { - this.registerManagedSocketFactory(resolverResult.value.authority.connectTo, proxy); - } + logInfo(`returned ${resolverResult.value.authority.connectTo}`); } else { logError(`returned an error`, resolverResult.error); } @@ -460,45 +428,6 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { } } - private registerManagedSocketFactory(messaging: ManagedRemoteConnection, proxy: IExtensionHostProxy) { - const that = this; - this._remoteSocketFactoryService.register(RemoteConnectionType.Managed, new class implements ISocketFactory { - - supports(connectTo: ManagedRemoteConnection): boolean { - return (connectTo.id === messaging.id); - } - - connect(connectTo: ManagedRemoteConnection, path: string, query: string, debugLabel: string): Promise { - return new Promise((resolve, reject) => { - if (connectTo.id !== messaging.id) { - return reject(new Error('Invalid connectTo')); - } - - const factoryId = connectTo.id; - 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, proxy, path, query, debugLabel, half) - .then( - socket => { - socket.onDidDispose(() => that._remoteSockets.delete(socketId)); - resolve(socket); - }, - err => { - that._remoteSockets.delete(socketId); - reject(err); - }); - }).catch(reject); - }); - } - }); - } - public async getCanonicalURI(remoteAuthority: string, uri: URI): Promise { const proxy = await this._proxy; if (!proxy) { diff --git a/src/vs/workbench/services/extensions/common/extensionHostProxy.ts b/src/vs/workbench/services/extensions/common/extensionHostProxy.ts index b6c8a8d24cc..e1c962a8b40 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostProxy.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostProxy.ts @@ -42,9 +42,4 @@ export interface IExtensionHostProxy { test_latency(n: number): Promise; test_up(b: VSBuffer): Promise; test_down(size: number): Promise; - - openRemoteSocket(factoryId: number): Promise; - remoteSocketWrite(socketId: number, buffer: VSBuffer): void; - remoteSocketEnd(socketId: number): void; - remoteSocketDrain(socketId: number): Promise; } diff --git a/src/vs/workbench/services/remote/common/managedSocket.ts b/src/vs/workbench/services/remote/common/managedSocket.ts deleted file mode 100644 index 4ff860a8f44..00000000000 --- a/src/vs/workbench/services/remote/common/managedSocket.ts +++ /dev/null @@ -1,91 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { VSBuffer } from 'vs/base/common/buffer'; -import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { ISocket, SocketCloseEvent, SocketDiagnostics, SocketDiagnosticsEventType } from 'vs/base/parts/ipc/common/ipc.net'; -import { makeRawSocketHeaders, socketRawEndHeaderSequence } from 'vs/platform/remote/common/managedSocket'; -import { IExtensionHostProxy } from 'vs/workbench/services/extensions/common/extensionHostProxy'; - -export class ManagedSocket extends Disposable implements ISocket { - public static connect( - socketId: number, - proxy: IExtensionHostProxy, - path: string, query: string, debugLabel: string, - - half: { - onClose: Emitter; - onData: Emitter; - onEnd: Emitter; - } - ): Promise { - 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((resolve, reject) => { - d.add(socket.onData(d => { - if (d.indexOf(socketRawEndHeaderSequence) !== -1) { - resolve(socket); - } - })); - - d.add(socket.onClose(err => reject(err ?? new Error('socket closed')))); - d.add(socket.onEnd(() => reject(new Error('socket ended')))); - }).finally(() => d.dispose()); - } - - public onData: Event; - public onClose: Event; - public onEnd: Event; - - private readonly didDisposeEmitter = this._register(new Emitter()); - public onDidDispose = this.didDisposeEmitter.event; - - private ended = false; - - private constructor( - private readonly socketId: number, - private readonly proxy: IExtensionHostProxy, - private readonly debugLabel: string, - onCloseEmitter: Emitter, - onDataEmitter: Emitter, - onEndEmitter: Emitter, - ) { - super(); - this.onClose = this._register(onCloseEmitter).event; - this.onData = this._register(onDataEmitter).event; - this.onEnd = this._register(onEndEmitter).event; - } - - write(buffer: VSBuffer): void { - this.proxy.remoteSocketWrite(this.socketId, buffer); - } - - end(): void { - this.ended = true; - this.proxy.remoteSocketEnd(this.socketId); - } - - drain(): Promise { - 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(); - } -} - From 12b39c303308ca810074a73080fe3e73afd9b0d2 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 11 May 2023 16:22:40 +0200 Subject: [PATCH 11/16] Delete only on close --- src/vs/workbench/api/browser/mainThreadManagedSockets.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/api/browser/mainThreadManagedSockets.ts b/src/vs/workbench/api/browser/mainThreadManagedSockets.ts index ef532391c60..4ad4c6d653f 100644 --- a/src/vs/workbench/api/browser/mainThreadManagedSockets.ts +++ b/src/vs/workbench/api/browser/mainThreadManagedSockets.ts @@ -88,7 +88,6 @@ export class MainThreadManagedSockets extends Disposable implements MainThreadMa $onDidManagedSocketEnd(socketId: number): void { this._remoteSockets.get(socketId)?.onEnd.fire(); - this._remoteSockets.delete(socketId); } } From 273b73d71371eaa8eb772e91e1fbc5f6c2640084 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 11 May 2023 16:24:21 +0200 Subject: [PATCH 12/16] :lipstick: reduce diff --- src/vs/workbench/contrib/webview/browser/webviewElement.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index 697b230d390..ee19ad8de81 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -782,10 +782,7 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD private async localLocalhost(id: string, origin: string) { const authority = this._environmentService.remoteAuthority; const resolveAuthority = authority ? await this._remoteAuthorityResolverService.resolveAuthority(authority) : undefined; - const redirect = resolveAuthority ? await this._portMappingManager.getRedirect({ - connectionToken: resolveAuthority.authority.connectionToken, - connectTo: resolveAuthority.authority.connectTo, - }, origin) : undefined; + const redirect = resolveAuthority ? await this._portMappingManager.getRedirect(resolveAuthority.authority, origin) : undefined; return this._send('did-load-localhost', { id, origin, From 66f719f819437944a4eea331f121b2ce6e138b05 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 11 May 2023 17:04:37 +0200 Subject: [PATCH 13/16] Share code via `AbstractExtensionService` --- .../extensions/browser/extensionService.ts | 102 ++---------------- .../common/abstractExtensionService.ts | 101 ++++++++++++++++- .../nativeExtensionService.ts | 99 ++--------------- .../test/browser/extensionService.test.ts | 10 +- 4 files changed, 123 insertions(+), 189 deletions(-) diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index 9d789d8f995..fa581f401e6 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { Schemas } from 'vs/base/common/network'; -import { StopWatch } from 'vs/base/common/stopwatch'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ExtensionKind } from 'vs/platform/environment/common/environment'; import { ExtensionIdentifier, ExtensionType, IExtension, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -16,7 +15,7 @@ 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 { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection'; -import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode, ResolverResult, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver'; +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'; @@ -27,7 +26,6 @@ import { IWebWorkerExtensionHostDataProvider, IWebWorkerExtensionHostInitData, W import { FetchFileSystemProvider } from 'vs/workbench/services/extensions/browser/webWorkerFileSystemProvider'; 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 { IResolveAuthorityErrorResult } from 'vs/workbench/services/extensions/common/extensionHostProxy'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; import { ExtensionRunningLocation } from 'vs/workbench/services/extensions/common/extensionRunningLocation'; import { ExtensionRunningLocationTracker, filterExtensionDescriptions } from 'vs/workbench/services/extensions/common/extensionRunningLocationTracker'; @@ -43,8 +41,6 @@ import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/c export class ExtensionService extends AbstractExtensionService implements IExtensionService { - private _resolveAuthorityAttempt: number = 0; - constructor( @IInstantiationService instantiationService: IInstantiationService, @INotificationService notificationService: INotificationService, @@ -62,7 +58,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten @IRemoteAgentService remoteAgentService: IRemoteAgentService, @IRemoteExtensionsScannerService remoteExtensionsScannerService: IRemoteExtensionsScannerService, @ILifecycleService lifecycleService: ILifecycleService, - @IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, + @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService, @IUserDataInitializationService private readonly _userDataInitializationService: IUserDataInitializationService, @IUserDataProfileService private readonly _userDataProfileService: IUserDataProfileService, @IWorkspaceTrustManagementService private readonly _workspaceTrustManagementService: IWorkspaceTrustManagementService, @@ -75,7 +71,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten () => this._getExtensions(), instantiationService, remoteAgentService, - _remoteAuthorityResolverService, + remoteAuthorityResolverService, extensionEnablementService ); super( @@ -96,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 @@ -205,93 +202,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten } } - // impl - - private async _resolveAuthorityAgain(): Promise { - 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 _resolveAuthorityInitial(remoteAuthority: string): Promise { - 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 _resolveAuthorityWithLogging(remoteAuthority: string): Promise { - 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.connectTo}' 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; - } - } - - private async _resolveAuthority(remoteAuthority: string): Promise { - const localWebWorkerExtensionHosts = this._getExtensionHostManagers(ExtensionHostKind.LocalWebWorker); - if (localWebWorkerExtensionHosts.length === 0) { - // no local process extension hosts - throw new Error(`Cannot resolve authority`); - } - - this._resolveAuthorityAttempt++; - const results = await Promise.all(localWebWorkerExtensionHosts.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); + protected async _resolveAuthority(remoteAuthority: string): Promise { + return this._resolveAuthorityOnExtensionHosts(ExtensionHostKind.LocalWebWorker, remoteAuthority); } } diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index cd829cd14f5..26dff791137 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -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 { + 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 { + 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 { + 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.connectTo}' 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 _resolveAuthorityOnExtensionHosts(kind: ExtensionHostKind, remoteAuthority: string): Promise { + + 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; @@ -1019,6 +1117,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx protected abstract _resolveExtensions(): Promise; protected abstract _scanSingleExtension(extension: IExtension): Promise; protected abstract _onExtensionHostExit(code: number): void; + protected abstract _resolveAuthority(remoteAuthority: string): Promise; } export class ResolvedExtensions { diff --git a/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts b/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts index b1499660215..e46774df584 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts @@ -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, RemoteConnectionType, 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 { + protected async _resolveAuthority(remoteAuthority: string): Promise { const authorityPlusIndex = remoteAuthority.indexOf('+'); if (authorityPlusIndex === -1) { @@ -291,33 +289,7 @@ export class NativeExtensionService extends AbstractExtensionService implements }; } - 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 { @@ -346,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 { - 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 { - 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 { - 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.connectTo}' 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 { this._extensionScanner.startScanningExtensions(); diff --git a/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts b/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts index 7e833bfaafd..c6833a93971 100644 --- a/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts +++ b/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts @@ -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 { + throw new Error('Method not implemented.'); + } } let disposables: DisposableStore; @@ -236,6 +243,7 @@ suite('ExtensionService', () => { [IUserDataProfileService, TestUserDataProfileService], [IUriIdentityService, UriIdentityService], [IRemoteExtensionsScannerService, TestRemoteExtensionsScannerService], + [IRemoteAuthorityResolverService, RemoteAuthorityResolverService] ]); extService = instantiationService.get(IExtensionService); }); From f76fd4f664330040c1e8d44b9b40fc352e4f098a Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 11 May 2023 12:29:42 -0700 Subject: [PATCH 14/16] address pr comments --- .../vscode-test-resolver/src/extension.ts | 4 +- src/vs/base/common/event.ts | 4 + .../api/browser/mainThreadManagedSockets.ts | 56 +++++++--- .../api/common/extHostManagedSockets.ts | 8 +- .../browser/mainThreadManagedSockets.test.ts | 104 ++++++++++++++++++ .../electron-sandbox/environmentService.ts | 2 +- .../remote/common/remoteExtensionsScanner.ts | 3 +- src/vscode-dts/vscode.proposed.resolvers.d.ts | 6 +- 8 files changed, 163 insertions(+), 24 deletions(-) create mode 100644 src/vs/workbench/api/test/browser/mainThreadManagedSockets.test.ts diff --git a/extensions/vscode-test-resolver/src/extension.ts b/extensions/vscode-test-resolver/src/extension.ts index 46f95f14f1f..4178c2b823e 100644 --- a/extensions/vscode-test-resolver/src/extension.ts +++ b/extensions/vscode-test-resolver/src/extension.ts @@ -196,8 +196,8 @@ export function activate(context: vscode.ExtensionContext) { onDidReceiveMessage: dataEmitter.event, onDidClose: closeEmitter.event, onDidEnd: endEmitter.event, - dataHandler: d => remoteSocket.write(d), - endHandler: () => remoteSocket.end(), + send: d => remoteSocket.write(d), + end: () => remoteSocket.end(), }; }, connectionToken)); } diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index 81981c52906..24547947c56 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -1171,6 +1171,10 @@ export class PauseableEmitter extends Emitter { protected _eventQueue = new LinkedList(); 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; diff --git a/src/vs/workbench/api/browser/mainThreadManagedSockets.ts b/src/vs/workbench/api/browser/mainThreadManagedSockets.ts index 4ad4c6d653f..dbd5c34ac3e 100644 --- a/src/vs/workbench/api/browser/mainThreadManagedSockets.ts +++ b/src/vs/workbench/api/browser/mainThreadManagedSockets.ts @@ -10,7 +10,7 @@ import { ManagedRemoteConnection, RemoteConnectionType } from 'vs/platform/remot 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 } from 'vs/base/common/event'; +import { Emitter, Event, PauseableEmitter } from 'vs/base/common/event'; import { makeRawSocketHeaders, socketRawEndHeaderSequence } from 'vs/platform/remote/common/managedSocket'; @extHostNamedCustomer(MainContext.MainThreadManagedSockets) @@ -30,7 +30,7 @@ export class MainThreadManagedSockets extends Disposable implements MainThreadMa async $registerSocketFactory(socketFactoryId: number): Promise { const that = this; - const scoketFactory = new class implements ISocketFactory { + const socketFactory = new class implements ISocketFactory { supports(connectTo: ManagedRemoteConnection): boolean { return (connectTo.id === socketFactoryId); @@ -65,7 +65,7 @@ export class MainThreadManagedSockets extends Disposable implements MainThreadMa }); } }; - this._registrations.set(socketFactoryId, this._remoteSocketFactoryService.register(RemoteConnectionType.Managed, scoketFactory)); + this._registrations.set(socketFactoryId, this._remoteSocketFactoryService.register(RemoteConnectionType.Managed, socketFactory)); } @@ -91,7 +91,7 @@ export class MainThreadManagedSockets extends Disposable implements MainThreadMa } } -interface RemoteSocketHalf { +export interface RemoteSocketHalf { onData: Emitter; onClose: Emitter; onEnd: Emitter; @@ -103,11 +103,7 @@ export class ManagedSocket extends Disposable implements ISocket { proxy: ExtHostManagedSocketsShape, path: string, query: string, debugLabel: string, - half: { - onClose: Emitter; - onData: Emitter; - onEnd: Emitter; - } + half: RemoteSocketHalf ): Promise { const socket = new ManagedSocket(socketId, proxy, debugLabel, half.onClose, half.onData, half.onEnd); @@ -115,9 +111,28 @@ export class ManagedSocket extends Disposable implements ISocket { const d = new DisposableStore(); return new Promise((resolve, reject) => { + let dataSoFar: VSBuffer | undefined; d.add(socket.onData(d => { - if (d.indexOf(socketRawEndHeaderSequence) !== -1) { - resolve(socket); + 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); } })); @@ -126,7 +141,14 @@ export class ManagedSocket extends Disposable implements ISocket { }).finally(() => d.dispose()); } - public onData: Event; + private readonly pausableDataEmitter = this._register(new PauseableEmitter()); + + public onData: Event = (...args) => { + if (this.pausableDataEmitter.isPaused) { + queueMicrotask(() => this.pausableDataEmitter.resume()); + } + return this.pausableDataEmitter.event(...args); + }; public onClose: Event; public onEnd: Event; @@ -144,11 +166,19 @@ export class ManagedSocket extends Disposable implements ISocket { onEndEmitter: Emitter, ) { super(); + + this._register(onDataEmitter); + this._register(onDataEmitter.event(data => this.pausableDataEmitter.fire(data))); + this.onClose = this._register(onCloseEmitter).event; - this.onData = this._register(onDataEmitter).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); } diff --git a/src/vs/workbench/api/common/extHostManagedSockets.ts b/src/vs/workbench/api/common/extHostManagedSockets.ts index ccf2c7be634..d2f028df19e 100644 --- a/src/vs/workbench/api/common/extHostManagedSockets.ts +++ b/src/vs/workbench/api/common/extHostManagedSockets.ts @@ -71,19 +71,19 @@ export class ExtHostManagedSockets implements IExtHostManagedSockets { } $remoteSocketWrite(socketId: number, buffer: VSBuffer): void { - this._managedRemoteSockets.get(socketId)?.actual.dataHandler(buffer.buffer); + this._managedRemoteSockets.get(socketId)?.actual.send(buffer.buffer); } $remoteSocketEnd(socketId: number): void { const socket = this._managedRemoteSockets.get(socketId); if (socket) { - socket.actual.endHandler(); + socket.actual.end(); socket.dispose(); } } - $remoteSocketDrain(socketId: number): Promise { - return this._managedRemoteSockets.get(socketId)?.actual.drainHandler?.() ?? Promise.resolve(); + async $remoteSocketDrain(socketId: number): Promise { + await this._managedRemoteSockets.get(socketId)?.actual.drain?.(); } } diff --git a/src/vs/workbench/api/test/browser/mainThreadManagedSockets.test.ts b/src/vs/workbench/api/test/browser/mainThreadManagedSockets.test.ts new file mode 100644 index 00000000000..611ec9727c0 --- /dev/null +++ b/src/vs/workbench/api/test/browser/mainThreadManagedSockets.test.ts @@ -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() { + private onDidFire = new Emitter(); + 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(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(), + onData: new Emitter(), + onEnd: new Emitter(), + }; + }); + + 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']); + }); + }); +}); diff --git a/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts b/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts index fec734ac73b..810a38b3866 100644 --- a/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts +++ b/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts @@ -63,7 +63,7 @@ export class NativeWorkbenchEnvironmentService extends AbstractNativeEnvironment get remoteAuthority() { return this.configuration.remoteAuthority; } @memoize - get expectsResolverExtension() { return !!this.configuration.remoteAuthority; } + get expectsResolverExtension() { return !!this.configuration.remoteAuthority?.includes('+'); } @memoize get execPath() { return this.configuration.execPath; } diff --git a/src/vs/workbench/services/remote/common/remoteExtensionsScanner.ts b/src/vs/workbench/services/remote/common/remoteExtensionsScanner.ts index a466cc1f3a1..418026d6a4f 100644 --- a/src/vs/workbench/services/remote/common/remoteExtensionsScanner.ts +++ b/src/vs/workbench/services/remote/common/remoteExtensionsScanner.ts @@ -7,7 +7,7 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA import { IRemoteExtensionsScannerService, RemoteExtensionsScannerChannelName } from 'vs/platform/remote/common/remoteExtensionsScanner'; import * as platform from 'vs/base/common/platform'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IExtensionDescription, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { IRemoteUserDataProfilesService } from 'vs/workbench/services/userDataProfile/common/remoteUserDataProfiles'; @@ -45,6 +45,7 @@ class RemoteExtensionsScannerService implements IRemoteExtensionsScannerService const scannedExtensions = await channel.call('scanExtensions', [platform.language, profileLocation, this.environmentService.extensionDevelopmentLocationURI, languagePack]); scannedExtensions.forEach((extension) => { extension.extensionLocation = URI.revive(extension.extensionLocation); + extension.identifier = new ExtensionIdentifier(extension.identifier.value); }); return scannedExtensions; }, diff --git a/src/vscode-dts/vscode.proposed.resolvers.d.ts b/src/vscode-dts/vscode.proposed.resolvers.d.ts index 027e85a99da..d7529dd871f 100644 --- a/src/vscode-dts/vscode.proposed.resolvers.d.ts +++ b/src/vscode-dts/vscode.proposed.resolvers.d.ts @@ -31,9 +31,9 @@ declare module 'vscode' { onDidClose: Event; onDidEnd: Event; - dataHandler: (data: Uint8Array) => void; - endHandler: () => void; - drainHandler?: () => void; + send: (data: Uint8Array) => void; + end: () => void; + drain?: () => Thenable; } export class ManagedResolvedAuthority { From cf93b3f60a3a25c352976af1377b6211527046e3 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 12 May 2023 10:24:43 +0200 Subject: [PATCH 15/16] Fix wrong used symbol --- .../services/extensions/common/abstractExtensionService.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index 26dff791137..33a46e24b6d 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -574,13 +574,13 @@ export abstract class AbstractExtensionService extends Disposable implements IEx const sw = StopWatch.create(false); this._logService.info(`Invoking resolveAuthority(${authorityPrefix})...`); try { - performance.mark(`code/willResolveAuthority/${authorityPrefix}`); + perf.mark(`code/willResolveAuthority/${authorityPrefix}`); const result = await this._resolveAuthority(remoteAuthority); - performance.mark(`code/didResolveAuthorityOK/${authorityPrefix}`); + perf.mark(`code/didResolveAuthorityOK/${authorityPrefix}`); this._logService.info(`resolveAuthority(${authorityPrefix}) returned '${result.authority.connectTo}' after ${sw.elapsed()} ms`); return result; } catch (err) { - performance.mark(`code/didResolveAuthorityError/${authorityPrefix}`); + perf.mark(`code/didResolveAuthorityError/${authorityPrefix}`); this._logService.error(`resolveAuthority(${authorityPrefix}) returned an error after ${sw.elapsed()} ms`, err); throw err; } From 482c4bf75ed246f396bc58c308cd3702d3296e23 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 12 May 2023 22:57:26 +0200 Subject: [PATCH 16/16] Avoid mangling `ExtensionIdentifier._lower` --- src/vs/platform/extensions/common/extensions.ts | 7 ++++++- .../services/remote/common/remoteExtensionsScanner.ts | 3 +-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index 4e427d56b8f..cfa0e3296f0 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -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; diff --git a/src/vs/workbench/services/remote/common/remoteExtensionsScanner.ts b/src/vs/workbench/services/remote/common/remoteExtensionsScanner.ts index 418026d6a4f..a466cc1f3a1 100644 --- a/src/vs/workbench/services/remote/common/remoteExtensionsScanner.ts +++ b/src/vs/workbench/services/remote/common/remoteExtensionsScanner.ts @@ -7,7 +7,7 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA import { IRemoteExtensionsScannerService, RemoteExtensionsScannerChannelName } from 'vs/platform/remote/common/remoteExtensionsScanner'; import * as platform from 'vs/base/common/platform'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { ExtensionIdentifier, IExtensionDescription, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { IExtensionDescription, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { IRemoteUserDataProfilesService } from 'vs/workbench/services/userDataProfile/common/remoteUserDataProfiles'; @@ -45,7 +45,6 @@ class RemoteExtensionsScannerService implements IRemoteExtensionsScannerService const scannedExtensions = await channel.call('scanExtensions', [platform.language, profileLocation, this.environmentService.extensionDevelopmentLocationURI, languagePack]); scannedExtensions.forEach((extension) => { extension.extensionLocation = URI.revive(extension.extensionLocation); - extension.identifier = new ExtensionIdentifier(extension.identifier.value); }); return scannedExtensions; },