Skip to content

Commit d10902d

Browse files
authored
Merge pull request microsoft#184239 from microsoft/alexd/busy-raven
Take into account already activated extensions when computing running locations
2 parents 8f93229 + c1b8d66 commit d10902d

File tree

8 files changed

+200
-12
lines changed

8 files changed

+200
-12
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
//@ts-check
7+
8+
'use strict';
9+
10+
const withBrowserDefaults = require('../shared.webpack.config').browser;
11+
12+
module.exports = withBrowserDefaults({
13+
context: __dirname,
14+
entry: {
15+
extension: './src/extension.browser.ts'
16+
},
17+
output: {
18+
filename: 'testResolverMain.js'
19+
}
20+
});

extensions/vscode-test-resolver/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44
"version": "0.0.1",
55
"publisher": "vscode",
66
"license": "MIT",
7-
"enableProposedApi": true,
87
"enabledApiProposals": [
98
"resolvers",
10-
"tunnels"
9+
"tunnels"
1110
],
1211
"private": true,
1312
"engines": {
@@ -32,6 +31,7 @@
3231
"onCommand:vscode-testresolver.toggleConnectionPause"
3332
],
3433
"main": "./out/extension",
34+
"browser": "./dist/browser/testResolverMain",
3535
"devDependencies": {
3636
"@types/node": "16.x"
3737
},
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as vscode from 'vscode';
7+
8+
export function activate(_context: vscode.ExtensionContext) {
9+
vscode.workspace.registerRemoteAuthorityResolver('test', {
10+
async resolve(_authority: string): Promise<vscode.ResolverResult> {
11+
console.log(`Resolving ${_authority}`);
12+
console.log(`Activating vscode.github-authentication to simulate auth`);
13+
await vscode.extensions.getExtension('vscode.github-authentication')?.activate();
14+
return new vscode.ManagedResolvedAuthority(async () => {
15+
return new InitialManagedMessagePassing();
16+
});
17+
}
18+
});
19+
}
20+
21+
/**
22+
* The initial message passing is a bit special because we need to
23+
* wait for the HTTP headers to arrive before we can create the
24+
* actual WebSocket.
25+
*/
26+
class InitialManagedMessagePassing implements vscode.ManagedMessagePassing {
27+
private readonly dataEmitter = new vscode.EventEmitter<Uint8Array>();
28+
private readonly closeEmitter = new vscode.EventEmitter<Error | undefined>();
29+
private readonly endEmitter = new vscode.EventEmitter<void>();
30+
31+
public readonly onDidReceiveMessage = this.dataEmitter.event;
32+
public readonly onDidClose = this.closeEmitter.event;
33+
public readonly onDidEnd = this.endEmitter.event;
34+
35+
private _actual: OpeningManagedMessagePassing | null = null;
36+
private _isDisposed = false;
37+
38+
public send(d: Uint8Array): void {
39+
if (this._actual) {
40+
// we already got the HTTP headers
41+
this._actual.send(d);
42+
return;
43+
}
44+
45+
if (this._isDisposed) {
46+
// got disposed in the meantime, ignore
47+
return;
48+
}
49+
50+
// we now received the HTTP headers
51+
const decoder = new TextDecoder();
52+
const str = decoder.decode(d);
53+
54+
// example str GET ws://localhost/oss-dev?reconnectionToken=4354a323-a45a-452c-b5d7-d8d586e1cd5c&reconnection=false&skipWebSocketFrames=true HTTP/1.1
55+
const match = str.match(/GET\s+(\S+)\s+HTTP/);
56+
if (!match) {
57+
console.error(`Coult not parse ${str}`);
58+
this.closeEmitter.fire(new Error(`Coult not parse ${str}`));
59+
return;
60+
}
61+
62+
// example url ws://localhost/oss-dev?reconnectionToken=4354a323-a45a-452c-b5d7-d8d586e1cd5c&reconnection=false&skipWebSocketFrames=true
63+
const url = new URL(match[1]);
64+
65+
// extract path and query from url using browser's URL
66+
const parsedUrl = new URL(url);
67+
this._actual = new OpeningManagedMessagePassing(parsedUrl, this.dataEmitter, this.closeEmitter, this.endEmitter);
68+
}
69+
70+
public end(): void {
71+
if (this._actual) {
72+
this._actual.end();
73+
return;
74+
}
75+
this._isDisposed = true;
76+
}
77+
}
78+
79+
class OpeningManagedMessagePassing {
80+
81+
private readonly socket: WebSocket;
82+
private isOpen = false;
83+
private bufferedData: Uint8Array[] = [];
84+
85+
constructor(
86+
url: URL,
87+
dataEmitter: vscode.EventEmitter<Uint8Array>,
88+
closeEmitter: vscode.EventEmitter<Error | undefined>,
89+
_endEmitter: vscode.EventEmitter<void>
90+
) {
91+
this.socket = new WebSocket(`ws://localhost:9888${url.pathname}${url.search.replace(/skipWebSocketFrames=true/, 'skipWebSocketFrames=false')}`);
92+
this.socket.addEventListener('close', () => closeEmitter.fire(undefined));
93+
this.socket.addEventListener('error', (e) => closeEmitter.fire(new Error(String(e))));
94+
this.socket.addEventListener('message', async (e) => {
95+
const arrayBuffer = await e.data.arrayBuffer();
96+
dataEmitter.fire(new Uint8Array(arrayBuffer));
97+
});
98+
this.socket.addEventListener('open', () => {
99+
while (this.bufferedData.length > 0) {
100+
const first = this.bufferedData.shift()!;
101+
this.socket.send(first);
102+
}
103+
this.isOpen = true;
104+
105+
// https://tools.ietf.org/html/rfc6455#section-4
106+
// const requestNonce = req.headers['sec-websocket-key'];
107+
// const hash = crypto.createHash('sha1');
108+
// hash.update(requestNonce + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11');
109+
// const responseNonce = hash.digest('base64');
110+
const responseHeaders = [
111+
`HTTP/1.1 101 Switching Protocols`,
112+
`Upgrade: websocket`,
113+
`Connection: Upgrade`,
114+
`Sec-WebSocket-Accept: TODO`
115+
];
116+
const textEncoder = new TextEncoder();
117+
textEncoder.encode(responseHeaders.join('\r\n') + '\r\n\r\n');
118+
dataEmitter.fire(textEncoder.encode(responseHeaders.join('\r\n') + '\r\n\r\n'));
119+
});
120+
}
121+
122+
public send(d: Uint8Array): void {
123+
if (!this.isOpen) {
124+
this.bufferedData.push(d);
125+
return;
126+
}
127+
this.socket.send(d);
128+
}
129+
130+
public end(): void {
131+
this.socket.close();
132+
}
133+
}

extensions/vscode-test-resolver/tsconfig.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
"outDir": "./out",
55
"types": [
66
"node"
7+
],
8+
"lib": [
9+
"WebWorker"
710
]
811
},
912
"include": [

src/vs/server/node/serverEnvironmentService.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export const serverOptions: OptionDescriptions<Required<ServerParsedArgs>> = {
4848

4949
'enable-sync': { type: 'boolean' },
5050
'github-auth': { type: 'string' },
51+
'use-test-resolver': { type: 'boolean' },
5152

5253
/* ----- extension management ----- */
5354

@@ -165,6 +166,7 @@ export interface ServerParsedArgs {
165166

166167
'enable-sync'?: boolean;
167168
'github-auth'?: string;
169+
'use-test-resolver'?: boolean;
168170

169171
/* ----- extension management ----- */
170172

src/vs/server/node/webClientServer.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { isLinux } from 'vs/base/common/platform';
1515
import { ILogService } from 'vs/platform/log/common/log';
1616
import { IServerEnvironmentService } from 'vs/server/node/serverEnvironmentService';
1717
import { extname, dirname, join, normalize } from 'vs/base/common/path';
18-
import { FileAccess, connectionTokenCookieName, connectionTokenQueryName, Schemas } from 'vs/base/common/network';
18+
import { FileAccess, connectionTokenCookieName, connectionTokenQueryName, Schemas, builtinExtensionsPath } from 'vs/base/common/network';
1919
import { generateUuid } from 'vs/base/common/uuid';
2020
import { IProductService } from 'vs/platform/product/common/productService';
2121
import { ServerConnectionToken, ServerConnectionTokenType } from 'vs/server/node/serverConnectionToken';
@@ -28,6 +28,7 @@ import { IProductConfiguration } from 'vs/base/common/product';
2828
import { isString } from 'vs/base/common/types';
2929
import { CharCode } from 'vs/base/common/charCode';
3030
import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts';
31+
import { IExtensionManifest } from 'vs/platform/extensions/common/extensions';
3132

3233
const textMimeType = {
3334
'.html': 'text/html',
@@ -272,7 +273,12 @@ export class WebClientServer {
272273
return Array.isArray(val) ? val[0] : val;
273274
};
274275

275-
const remoteAuthority = getFirstHeader('x-original-host') || getFirstHeader('x-forwarded-host') || req.headers.host;
276+
const useTestResolver = (!this._environmentService.isBuilt && this._environmentService.args['use-test-resolver']);
277+
const remoteAuthority = (
278+
useTestResolver
279+
? 'test+test'
280+
: (getFirstHeader('x-original-host') || getFirstHeader('x-forwarded-host') || req.headers.host)
281+
);
276282
if (!remoteAuthority) {
277283
return serveError(req, res, 400, `Bad request.`);
278284
}
@@ -337,6 +343,14 @@ export class WebClientServer {
337343
WORKBENCH_NLS_BASE_URL: nlsBaseUrl ? `${nlsBaseUrl}${!nlsBaseUrl.endsWith('/') ? '/' : ''}${this._productService.commit}/${this._productService.version}/` : '',
338344
};
339345

346+
if (useTestResolver) {
347+
const bundledExtensions: { extensionPath: string; packageJSON: IExtensionManifest }[] = [];
348+
for (const extensionPath of ['vscode-test-resolver', 'github-authentication']) {
349+
const packageJSON = JSON.parse((await fsp.readFile(FileAccess.asFileUri(`${builtinExtensionsPath}/${extensionPath}/package.json`).fsPath)).toString());
350+
bundledExtensions.push({ extensionPath, packageJSON });
351+
}
352+
values['WORKBENCH_BUILTIN_EXTENSIONS'] = asJSON(bundledExtensions);
353+
}
340354

341355
let data;
342356
try {
@@ -351,7 +365,7 @@ export class WebClientServer {
351365
'default-src \'self\';',
352366
'img-src \'self\' https: data: blob:;',
353367
'media-src \'self\';',
354-
`script-src 'self' 'unsafe-eval' ${this._getScriptCspHashes(data).join(' ')} 'sha256-fh3TwPMflhsEIpR8g1OYTIMVWhXTLcjQ9kh2tIpmv54=' http://${remoteAuthority};`, // the sha is the same as in src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html
368+
`script-src 'self' 'unsafe-eval' ${this._getScriptCspHashes(data).join(' ')} 'sha256-fh3TwPMflhsEIpR8g1OYTIMVWhXTLcjQ9kh2tIpmv54=' ${useTestResolver ? '' : `http://${remoteAuthority}`};`, // the sha is the same as in src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html
355369
'child-src \'self\';',
356370
`frame-src 'self' https://*.vscode-cdn.net data:;`,
357371
'worker-src \'self\' data: blob:;',

src/vs/workbench/services/extensions/common/abstractExtensionService.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -738,7 +738,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
738738
}
739739

740740
protected _doCreateExtensionHostManager(extensionHost: IExtensionHost, initialActivationEvents: string[]): IExtensionHostManager {
741-
return createExtensionHostManager(this._instantiationService, extensionHost, initialActivationEvents, this._acquireInternalAPI());
741+
return createExtensionHostManager(this._instantiationService, extensionHost, initialActivationEvents, this._acquireInternalAPI(extensionHost));
742742
}
743743

744744
private _onExtensionHostCrashOrExit(extensionHost: IExtensionHostManager, code: number, signal: string | null): void {
@@ -1070,13 +1070,13 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
10701070

10711071
//#region Called by extension host
10721072

1073-
private _acquireInternalAPI(): IInternalExtensionService {
1073+
private _acquireInternalAPI(extensionHost: IExtensionHost): IInternalExtensionService {
10741074
return {
10751075
_activateById: (extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void> => {
10761076
return this._activateById(extensionId, reason);
10771077
},
10781078
_onWillActivateExtension: (extensionId: ExtensionIdentifier): void => {
1079-
return this._onWillActivateExtension(extensionId);
1079+
return this._onWillActivateExtension(extensionId, extensionHost.runningLocation);
10801080
},
10811081
_onDidActivateExtension: (extensionId: ExtensionIdentifier, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationReason: ExtensionActivationReason): void => {
10821082
return this._onDidActivateExtension(extensionId, codeLoadingTime, activateCallTime, activateResolvedTime, activationReason);
@@ -1100,7 +1100,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
11001100
}
11011101
}
11021102

1103-
private _onWillActivateExtension(extensionId: ExtensionIdentifier): void {
1103+
private _onWillActivateExtension(extensionId: ExtensionIdentifier, runningLocation: ExtensionRunningLocation): void {
1104+
this._runningLocations.set(extensionId, runningLocation);
11041105
const extensionStatus = this._getOrCreateExtensionStatus(extensionId);
11051106
extensionStatus.onWillActivate();
11061107
}

src/vs/workbench/services/extensions/common/extensionRunningLocationTracker.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ export class ExtensionRunningLocationTracker {
3838
@IExtensionManifestPropertiesService private readonly _extensionManifestPropertiesService: IExtensionManifestPropertiesService,
3939
) { }
4040

41+
public set(extensionId: ExtensionIdentifier, runningLocation: ExtensionRunningLocation) {
42+
this._runningLocation.set(extensionId, runningLocation);
43+
}
44+
4145
public readExtensionKinds(extensionDescription: IExtensionDescription): ExtensionKind[] {
4246
if (extensionDescription.isUnderDevelopment && this._environmentService.extensionDevelopmentKind) {
4347
return this._environmentService.extensionDevelopmentKind;
@@ -196,10 +200,14 @@ export class ExtensionRunningLocationTracker {
196200
}
197201

198202
public computeRunningLocation(localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[], isInitialAllocation: boolean): ExtensionIdentifierMap<ExtensionRunningLocation | null> {
199-
return this._doComputeRunningLocation(localExtensions, remoteExtensions, isInitialAllocation).runningLocation;
203+
return this._doComputeRunningLocation(this._runningLocation, localExtensions, remoteExtensions, isInitialAllocation).runningLocation;
200204
}
201205

202-
private _doComputeRunningLocation(localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[], isInitialAllocation: boolean): { runningLocation: ExtensionIdentifierMap<ExtensionRunningLocation | null>; maxLocalProcessAffinity: number; maxLocalWebWorkerAffinity: number } {
206+
private _doComputeRunningLocation(existingRunningLocation: ExtensionIdentifierMap<ExtensionRunningLocation | null>, localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[], isInitialAllocation: boolean): { runningLocation: ExtensionIdentifierMap<ExtensionRunningLocation | null>; maxLocalProcessAffinity: number; maxLocalWebWorkerAffinity: number } {
207+
// Skip extensions that have an existing running location
208+
localExtensions = localExtensions.filter(extension => !existingRunningLocation.has(extension.identifier));
209+
remoteExtensions = remoteExtensions.filter(extension => !existingRunningLocation.has(extension.identifier));
210+
203211
const extensionHostKinds = determineExtensionHostKinds(
204212
localExtensions,
205213
remoteExtensions,
@@ -247,11 +255,18 @@ export class ExtensionRunningLocationTracker {
247255
result.set(extension.identifier, new LocalWebWorkerRunningLocation(affinity));
248256
}
249257

258+
// Add extensions that already have an existing running location
259+
for (const [extensionIdKey, runningLocation] of existingRunningLocation) {
260+
if (runningLocation) {
261+
result.set(extensionIdKey, runningLocation);
262+
}
263+
}
264+
250265
return { runningLocation: result, maxLocalProcessAffinity: maxAffinity, maxLocalWebWorkerAffinity: maxLocalWebWorkerAffinity };
251266
}
252267

253268
public initializeRunningLocation(localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[]): void {
254-
const { runningLocation, maxLocalProcessAffinity, maxLocalWebWorkerAffinity } = this._doComputeRunningLocation(localExtensions, remoteExtensions, true);
269+
const { runningLocation, maxLocalProcessAffinity, maxLocalWebWorkerAffinity } = this._doComputeRunningLocation(this._runningLocation, localExtensions, remoteExtensions, true);
255270
this._runningLocation = runningLocation;
256271
this._maxLocalProcessAffinity = maxLocalProcessAffinity;
257272
this._maxLocalWebWorkerAffinity = maxLocalWebWorkerAffinity;

0 commit comments

Comments
 (0)