Skip to content

Commit ba7435e

Browse files
authored
Merge pull request microsoft#180263 from microsoft/connor4312/inline-remote-resolver
remote: first cut at 'inline' remote resolvers
2 parents 9e5ddd4 + 482c4bf commit ba7435e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1264
-387
lines changed

extensions/vscode-test-resolver/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@
6666
"category": "Remote-TestResolver",
6767
"command": "vscode-testresolver.currentWindow"
6868
},
69+
{
70+
"title": "Connect to TestResolver in Current Window with Managed Connection",
71+
"category": "Remote-TestResolver",
72+
"command": "vscode-testresolver.currentWindowManaged"
73+
},
6974
{
7075
"title": "Show TestResolver Log",
7176
"category": "Remote-TestResolver",

extensions/vscode-test-resolver/src/extension.ts

Lines changed: 63 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,30 @@ export function activate(context: vscode.ExtensionContext) {
2727
let connectionPaused = false;
2828
const connectionPausedEvent = new vscode.EventEmitter<boolean>();
2929

30-
function doResolve(_authority: string, progress: vscode.Progress<{ message?: string; increment?: number }>): Promise<vscode.ResolvedAuthority> {
30+
function getTunnelFeatures(): vscode.TunnelInformation['tunnelFeatures'] {
31+
return {
32+
elevation: true,
33+
privacyOptions: vscode.workspace.getConfiguration('testresolver').get('supportPublicPorts') ? [
34+
{
35+
id: 'public',
36+
label: 'Public',
37+
themeIcon: 'eye'
38+
},
39+
{
40+
id: 'other',
41+
label: 'Other',
42+
themeIcon: 'circuit-board'
43+
},
44+
{
45+
id: 'private',
46+
label: 'Private',
47+
themeIcon: 'eye-closed'
48+
}
49+
] : []
50+
};
51+
}
52+
53+
function doResolve(authority: string, progress: vscode.Progress<{ message?: string; increment?: number }>): Promise<vscode.ResolverResult> {
3154
if (connectionPaused) {
3255
throw vscode.RemoteAuthorityResolverError.TemporarilyNotAvailable('Not available right now');
3356
}
@@ -150,7 +173,35 @@ export function activate(context: vscode.ExtensionContext) {
150173
}
151174
});
152175
});
153-
return serverPromise.then(serverAddr => {
176+
177+
return serverPromise.then((serverAddr): Promise<vscode.ResolverResult> => {
178+
if (authority.includes('managed')) {
179+
console.log('Connecting via a managed authority');
180+
return Promise.resolve(new vscode.ManagedResolvedAuthority(async () => {
181+
const remoteSocket = net.createConnection({ port: serverAddr.port });
182+
const dataEmitter = new vscode.EventEmitter<Uint8Array>();
183+
const closeEmitter = new vscode.EventEmitter<Error | undefined>();
184+
const endEmitter = new vscode.EventEmitter<void>();
185+
186+
await new Promise((res, rej) => {
187+
remoteSocket.on('data', d => dataEmitter.fire(d))
188+
.on('error', err => { rej(); closeEmitter.fire(err); })
189+
.on('close', () => endEmitter.fire())
190+
.on('end', () => endEmitter.fire())
191+
.on('connect', res);
192+
});
193+
194+
195+
return {
196+
onDidReceiveMessage: dataEmitter.event,
197+
onDidClose: closeEmitter.event,
198+
onDidEnd: endEmitter.event,
199+
send: d => remoteSocket.write(d),
200+
end: () => remoteSocket.end(),
201+
};
202+
}, connectionToken));
203+
}
204+
154205
return new Promise<vscode.ResolvedAuthority>((res, _rej) => {
155206
const proxyServer = net.createServer(proxySocket => {
156207
outputChannel.appendLine(`Proxy connection accepted`);
@@ -228,28 +279,7 @@ export function activate(context: vscode.ExtensionContext) {
228279
proxyServer.listen(0, '127.0.0.1', () => {
229280
const port = (<net.AddressInfo>proxyServer.address()).port;
230281
outputChannel.appendLine(`Going through proxy at port ${port}`);
231-
const r: vscode.ResolverResult = new vscode.ResolvedAuthority('127.0.0.1', port, connectionToken);
232-
r.tunnelFeatures = {
233-
elevation: true,
234-
privacyOptions: vscode.workspace.getConfiguration('testresolver').get('supportPublicPorts') ? [
235-
{
236-
id: 'public',
237-
label: 'Public',
238-
themeIcon: 'eye'
239-
},
240-
{
241-
id: 'other',
242-
label: 'Other',
243-
themeIcon: 'circuit-board'
244-
},
245-
{
246-
id: 'private',
247-
label: 'Private',
248-
themeIcon: 'eye-closed'
249-
}
250-
] : []
251-
};
252-
res(r);
282+
res(new vscode.ResolvedAuthority('127.0.0.1', port, connectionToken));
253283
});
254284
context.subscriptions.push({
255285
dispose: () => {
@@ -264,12 +294,16 @@ export function activate(context: vscode.ExtensionContext) {
264294
async getCanonicalURI(uri: vscode.Uri): Promise<vscode.Uri> {
265295
return vscode.Uri.file(uri.path);
266296
},
267-
resolve(_authority: string): Thenable<vscode.ResolvedAuthority> {
297+
resolve(_authority: string): Thenable<vscode.ResolverResult> {
268298
return vscode.window.withProgress({
269299
location: vscode.ProgressLocation.Notification,
270300
title: 'Open TestResolver Remote ([details](command:vscode-testresolver.showLog))',
271301
cancellable: false
272-
}, (progress) => doResolve(_authority, progress));
302+
}, async (progress) => {
303+
const rr = await doResolve(_authority, progress);
304+
rr.tunnelFeatures = getTunnelFeatures();
305+
return rr;
306+
});
273307
},
274308
tunnelFactory,
275309
showCandidatePort
@@ -282,6 +316,9 @@ export function activate(context: vscode.ExtensionContext) {
282316
context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.currentWindow', () => {
283317
return vscode.commands.executeCommand('vscode.newWindow', { remoteAuthority: 'test+test', reuseWindow: true });
284318
}));
319+
context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.currentWindowManaged', () => {
320+
return vscode.commands.executeCommand('vscode.newWindow', { remoteAuthority: 'test+managed', reuseWindow: true });
321+
}));
285322
context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.newWindowWithError', () => {
286323
return vscode.commands.executeCommand('vscode.newWindow', { remoteAuthority: 'test+error' });
287324
}));

src/vs/base/common/async.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1406,26 +1406,34 @@ export class IntervalCounter {
14061406

14071407
export type ValueCallback<T = unknown> = (value: T | Promise<T>) => void;
14081408

1409+
const enum DeferredOutcome {
1410+
Resolved,
1411+
Rejected
1412+
}
1413+
14091414
/**
14101415
* Creates a promise whose resolution or rejection can be controlled imperatively.
14111416
*/
14121417
export class DeferredPromise<T> {
14131418

14141419
private completeCallback!: ValueCallback<T>;
14151420
private errorCallback!: (err: unknown) => void;
1416-
private rejected = false;
1417-
private resolved = false;
1421+
private outcome?: { outcome: DeferredOutcome.Rejected; value: any } | { outcome: DeferredOutcome.Resolved; value: T };
14181422

14191423
public get isRejected() {
1420-
return this.rejected;
1424+
return this.outcome?.outcome === DeferredOutcome.Rejected;
14211425
}
14221426

14231427
public get isResolved() {
1424-
return this.resolved;
1428+
return this.outcome?.outcome === DeferredOutcome.Resolved;
14251429
}
14261430

14271431
public get isSettled() {
1428-
return this.rejected || this.resolved;
1432+
return !!this.outcome;
1433+
}
1434+
1435+
public get value() {
1436+
return this.outcome?.outcome === DeferredOutcome.Resolved ? this.outcome?.value : undefined;
14291437
}
14301438

14311439
public readonly p: Promise<T>;
@@ -1440,25 +1448,21 @@ export class DeferredPromise<T> {
14401448
public complete(value: T) {
14411449
return new Promise<void>(resolve => {
14421450
this.completeCallback(value);
1443-
this.resolved = true;
1451+
this.outcome = { outcome: DeferredOutcome.Resolved, value };
14441452
resolve();
14451453
});
14461454
}
14471455

14481456
public error(err: unknown) {
14491457
return new Promise<void>(resolve => {
14501458
this.errorCallback(err);
1451-
this.rejected = true;
1459+
this.outcome = { outcome: DeferredOutcome.Rejected, value: err };
14521460
resolve();
14531461
});
14541462
}
14551463

14561464
public cancel() {
1457-
new Promise<void>(resolve => {
1458-
this.errorCallback(new CancellationError());
1459-
this.rejected = true;
1460-
resolve();
1461-
});
1465+
return this.error(new CancellationError());
14621466
}
14631467
}
14641468

src/vs/base/common/buffer.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { Lazy } from 'vs/base/common/lazy';
67
import * as streams from 'vs/base/common/stream';
78

89
declare const Buffer: any;
910

1011
const hasBuffer = (typeof Buffer !== 'undefined');
12+
const indexOfTable = new Lazy(() => new Uint8Array(256));
1113

1214
let textEncoder: TextEncoder | null;
1315
let textDecoder: TextDecoder | null;
@@ -169,6 +171,52 @@ export class VSBuffer {
169171
writeUInt8(value: number, offset: number): void {
170172
writeUInt8(this.buffer, value, offset);
171173
}
174+
175+
indexOf(subarray: VSBuffer | Uint8Array) {
176+
const needle = subarray instanceof VSBuffer ? subarray.buffer : subarray;
177+
const needleLen = needle.byteLength;
178+
const haystack = this.buffer;
179+
const haystackLen = haystack.byteLength;
180+
181+
if (needleLen === 0) {
182+
return 0;
183+
}
184+
185+
if (needleLen === 1) {
186+
return haystack.indexOf(needle[0]);
187+
}
188+
189+
if (needleLen > haystackLen) {
190+
return -1;
191+
}
192+
193+
// find index of the subarray using boyer-moore-horspool algorithm
194+
const table = indexOfTable.value;
195+
table.fill(needle.length);
196+
for (let i = 0; i < needle.length; i++) {
197+
table[needle[i]] = needle.length - i - 1;
198+
}
199+
200+
let i = needle.length - 1;
201+
let j = i;
202+
let result = -1;
203+
while (i < haystackLen) {
204+
if (haystack[i] === needle[j]) {
205+
if (j === 0) {
206+
result = i;
207+
break;
208+
}
209+
210+
i--;
211+
j--;
212+
} else {
213+
i += Math.max(needle.length - j, table[haystack[i]]);
214+
j = needle.length - 1;
215+
}
216+
}
217+
218+
return result;
219+
}
172220
}
173221

174222
export function readUInt16LE(source: Uint8Array, offset: number): number {

src/vs/base/common/event.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,6 +1171,10 @@ export class PauseableEmitter<T> extends Emitter<T> {
11711171
protected _eventQueue = new LinkedList<T>();
11721172
private _mergeFn?: (input: T[]) => T;
11731173

1174+
public get isPaused(): boolean {
1175+
return this._isPaused !== 0;
1176+
}
1177+
11741178
constructor(options?: EmitterOptions & { merge?: (input: T[]) => T }) {
11751179
super(options);
11761180
this._mergeFn = options?.merge;

src/vs/base/test/common/buffer.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,22 @@ suite('Buffer', () => {
413413
}
414414
});
415415

416+
test('indexOf', () => {
417+
const haystack = VSBuffer.fromString('abcaabbccaaabbbccc');
418+
assert.strictEqual(haystack.indexOf(VSBuffer.fromString('')), 0);
419+
assert.strictEqual(haystack.indexOf(VSBuffer.fromString('a'.repeat(100))), -1);
420+
421+
assert.strictEqual(haystack.indexOf(VSBuffer.fromString('a')), 0);
422+
assert.strictEqual(haystack.indexOf(VSBuffer.fromString('c')), 2);
423+
424+
assert.strictEqual(haystack.indexOf(VSBuffer.fromString('abcaa')), 0);
425+
assert.strictEqual(haystack.indexOf(VSBuffer.fromString('caaab')), 8);
426+
assert.strictEqual(haystack.indexOf(VSBuffer.fromString('ccc')), 15);
427+
428+
assert.strictEqual(haystack.indexOf(VSBuffer.fromString('cccb')), -1);
429+
430+
});
431+
416432
suite('base64', () => {
417433
/*
418434
Generated with:

src/vs/platform/extensions/common/extensions.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,12 @@ export interface IExtension {
341341
*/
342342
export class ExtensionIdentifier {
343343
public readonly value: string;
344-
private readonly _lower: string;
344+
345+
/**
346+
* Do not use directly. This is public to avoid mangling and thus
347+
* allow compatibility between running from source and a built version.
348+
*/
349+
readonly _lower: string;
345350

346351
constructor(value: string) {
347352
this.value = value;

src/vs/platform/remote/browser/browserSocketFactory.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import { VSBuffer } from 'vs/base/common/buffer';
99
import { Emitter, Event } from 'vs/base/common/event';
1010
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
1111
import { ISocket, SocketCloseEvent, SocketCloseEventType, SocketDiagnostics, SocketDiagnosticsEventType } from 'vs/base/parts/ipc/common/ipc.net';
12-
import { IConnectCallback, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
13-
import { RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver';
12+
import { ISocketFactory } from 'vs/platform/remote/common/remoteSocketFactoryService';
13+
import { RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode, RemoteConnectionType, WebSocketRemoteConnection } from 'vs/platform/remote/common/remoteAuthorityResolver';
1414

1515
export interface IWebSocketFactory {
1616
create(url: string, debugLabel: string): IWebSocket;
@@ -265,23 +265,27 @@ class BrowserSocket implements ISocket {
265265
}
266266

267267

268-
export class BrowserSocketFactory implements ISocketFactory {
268+
export class BrowserSocketFactory implements ISocketFactory<RemoteConnectionType.WebSocket> {
269+
269270
private readonly _webSocketFactory: IWebSocketFactory;
270271

271272
constructor(webSocketFactory: IWebSocketFactory | null | undefined) {
272273
this._webSocketFactory = webSocketFactory || defaultWebSocketFactory;
273274
}
274275

275-
connect(host: string, port: number, path: string, query: string, debugLabel: string, callback: IConnectCallback): void {
276-
const webSocketSchema = (/^https:/.test(window.location.href) ? 'wss' : 'ws');
277-
const socket = this._webSocketFactory.create(`${webSocketSchema}://${(/:/.test(host) && !/\[/.test(host)) ? `[${host}]` : host}:${port}${path}?${query}&skipWebSocketFrames=false`, debugLabel);
278-
const errorListener = socket.onError((err) => callback(err, undefined));
279-
socket.onOpen(() => {
280-
errorListener.dispose();
281-
callback(undefined, new BrowserSocket(socket, debugLabel));
276+
supports(connectTo: WebSocketRemoteConnection): boolean {
277+
return true;
278+
}
279+
280+
connect({ host, port }: WebSocketRemoteConnection, path: string, query: string, debugLabel: string): Promise<ISocket> {
281+
return new Promise<ISocket>((resolve, reject) => {
282+
const webSocketSchema = (/^https:/.test(window.location.href) ? 'wss' : 'ws');
283+
const socket = this._webSocketFactory.create(`${webSocketSchema}://${(/:/.test(host) && !/\[/.test(host)) ? `[${host}]` : host}:${port}${path}?${query}&skipWebSocketFrames=false`, debugLabel);
284+
const errorListener = socket.onError(reject);
285+
socket.onOpen(() => {
286+
errorListener.dispose();
287+
resolve(new BrowserSocket(socket, debugLabel));
288+
});
282289
});
283290
}
284291
}
285-
286-
287-

0 commit comments

Comments
 (0)