Merge pull request #184239 from microsoft/alexd/busy-raven

Take into account already activated extensions when computing running locations
This commit is contained in:
Alexandru Dima 2023-06-05 15:04:59 +02:00 committed by GitHub
commit d10902d080
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 200 additions and 12 deletions

View file

@ -0,0 +1,20 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
//@ts-check
'use strict';
const withBrowserDefaults = require('../shared.webpack.config').browser;
module.exports = withBrowserDefaults({
context: __dirname,
entry: {
extension: './src/extension.browser.ts'
},
output: {
filename: 'testResolverMain.js'
}
});

View file

@ -4,10 +4,9 @@
"version": "0.0.1",
"publisher": "vscode",
"license": "MIT",
"enableProposedApi": true,
"enabledApiProposals": [
"resolvers",
"tunnels"
"tunnels"
],
"private": true,
"engines": {
@ -32,6 +31,7 @@
"onCommand:vscode-testresolver.toggleConnectionPause"
],
"main": "./out/extension",
"browser": "./dist/browser/testResolverMain",
"devDependencies": {
"@types/node": "16.x"
},

View file

@ -0,0 +1,133 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
export function activate(_context: vscode.ExtensionContext) {
vscode.workspace.registerRemoteAuthorityResolver('test', {
async resolve(_authority: string): Promise<vscode.ResolverResult> {
console.log(`Resolving ${_authority}`);
console.log(`Activating vscode.github-authentication to simulate auth`);
await vscode.extensions.getExtension('vscode.github-authentication')?.activate();
return new vscode.ManagedResolvedAuthority(async () => {
return new InitialManagedMessagePassing();
});
}
});
}
/**
* The initial message passing is a bit special because we need to
* wait for the HTTP headers to arrive before we can create the
* actual WebSocket.
*/
class InitialManagedMessagePassing implements vscode.ManagedMessagePassing {
private readonly dataEmitter = new vscode.EventEmitter<Uint8Array>();
private readonly closeEmitter = new vscode.EventEmitter<Error | undefined>();
private readonly endEmitter = new vscode.EventEmitter<void>();
public readonly onDidReceiveMessage = this.dataEmitter.event;
public readonly onDidClose = this.closeEmitter.event;
public readonly onDidEnd = this.endEmitter.event;
private _actual: OpeningManagedMessagePassing | null = null;
private _isDisposed = false;
public send(d: Uint8Array): void {
if (this._actual) {
// we already got the HTTP headers
this._actual.send(d);
return;
}
if (this._isDisposed) {
// got disposed in the meantime, ignore
return;
}
// we now received the HTTP headers
const decoder = new TextDecoder();
const str = decoder.decode(d);
// example str GET ws://localhost/oss-dev?reconnectionToken=4354a323-a45a-452c-b5d7-d8d586e1cd5c&reconnection=false&skipWebSocketFrames=true HTTP/1.1
const match = str.match(/GET\s+(\S+)\s+HTTP/);
if (!match) {
console.error(`Coult not parse ${str}`);
this.closeEmitter.fire(new Error(`Coult not parse ${str}`));
return;
}
// example url ws://localhost/oss-dev?reconnectionToken=4354a323-a45a-452c-b5d7-d8d586e1cd5c&reconnection=false&skipWebSocketFrames=true
const url = new URL(match[1]);
// extract path and query from url using browser's URL
const parsedUrl = new URL(url);
this._actual = new OpeningManagedMessagePassing(parsedUrl, this.dataEmitter, this.closeEmitter, this.endEmitter);
}
public end(): void {
if (this._actual) {
this._actual.end();
return;
}
this._isDisposed = true;
}
}
class OpeningManagedMessagePassing {
private readonly socket: WebSocket;
private isOpen = false;
private bufferedData: Uint8Array[] = [];
constructor(
url: URL,
dataEmitter: vscode.EventEmitter<Uint8Array>,
closeEmitter: vscode.EventEmitter<Error | undefined>,
_endEmitter: vscode.EventEmitter<void>
) {
this.socket = new WebSocket(`ws://localhost:9888${url.pathname}${url.search.replace(/skipWebSocketFrames=true/, 'skipWebSocketFrames=false')}`);
this.socket.addEventListener('close', () => closeEmitter.fire(undefined));
this.socket.addEventListener('error', (e) => closeEmitter.fire(new Error(String(e))));
this.socket.addEventListener('message', async (e) => {
const arrayBuffer = await e.data.arrayBuffer();
dataEmitter.fire(new Uint8Array(arrayBuffer));
});
this.socket.addEventListener('open', () => {
while (this.bufferedData.length > 0) {
const first = this.bufferedData.shift()!;
this.socket.send(first);
}
this.isOpen = true;
// https://tools.ietf.org/html/rfc6455#section-4
// const requestNonce = req.headers['sec-websocket-key'];
// const hash = crypto.createHash('sha1');
// hash.update(requestNonce + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11');
// const responseNonce = hash.digest('base64');
const responseHeaders = [
`HTTP/1.1 101 Switching Protocols`,
`Upgrade: websocket`,
`Connection: Upgrade`,
`Sec-WebSocket-Accept: TODO`
];
const textEncoder = new TextEncoder();
textEncoder.encode(responseHeaders.join('\r\n') + '\r\n\r\n');
dataEmitter.fire(textEncoder.encode(responseHeaders.join('\r\n') + '\r\n\r\n'));
});
}
public send(d: Uint8Array): void {
if (!this.isOpen) {
this.bufferedData.push(d);
return;
}
this.socket.send(d);
}
public end(): void {
this.socket.close();
}
}

View file

@ -4,6 +4,9 @@
"outDir": "./out",
"types": [
"node"
],
"lib": [
"WebWorker"
]
},
"include": [

View file

@ -48,6 +48,7 @@ export const serverOptions: OptionDescriptions<Required<ServerParsedArgs>> = {
'enable-sync': { type: 'boolean' },
'github-auth': { type: 'string' },
'use-test-resolver': { type: 'boolean' },
/* ----- extension management ----- */
@ -165,6 +166,7 @@ export interface ServerParsedArgs {
'enable-sync'?: boolean;
'github-auth'?: string;
'use-test-resolver'?: boolean;
/* ----- extension management ----- */

View file

@ -15,7 +15,7 @@ import { isLinux } from 'vs/base/common/platform';
import { ILogService } from 'vs/platform/log/common/log';
import { IServerEnvironmentService } from 'vs/server/node/serverEnvironmentService';
import { extname, dirname, join, normalize } from 'vs/base/common/path';
import { FileAccess, connectionTokenCookieName, connectionTokenQueryName, Schemas } from 'vs/base/common/network';
import { FileAccess, connectionTokenCookieName, connectionTokenQueryName, Schemas, builtinExtensionsPath } from 'vs/base/common/network';
import { generateUuid } from 'vs/base/common/uuid';
import { IProductService } from 'vs/platform/product/common/productService';
import { ServerConnectionToken, ServerConnectionTokenType } from 'vs/server/node/serverConnectionToken';
@ -28,6 +28,7 @@ import { IProductConfiguration } from 'vs/base/common/product';
import { isString } from 'vs/base/common/types';
import { CharCode } from 'vs/base/common/charCode';
import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts';
import { IExtensionManifest } from 'vs/platform/extensions/common/extensions';
const textMimeType = {
'.html': 'text/html',
@ -272,7 +273,12 @@ export class WebClientServer {
return Array.isArray(val) ? val[0] : val;
};
const remoteAuthority = getFirstHeader('x-original-host') || getFirstHeader('x-forwarded-host') || req.headers.host;
const useTestResolver = (!this._environmentService.isBuilt && this._environmentService.args['use-test-resolver']);
const remoteAuthority = (
useTestResolver
? 'test+test'
: (getFirstHeader('x-original-host') || getFirstHeader('x-forwarded-host') || req.headers.host)
);
if (!remoteAuthority) {
return serveError(req, res, 400, `Bad request.`);
}
@ -337,6 +343,14 @@ export class WebClientServer {
WORKBENCH_NLS_BASE_URL: nlsBaseUrl ? `${nlsBaseUrl}${!nlsBaseUrl.endsWith('/') ? '/' : ''}${this._productService.commit}/${this._productService.version}/` : '',
};
if (useTestResolver) {
const bundledExtensions: { extensionPath: string; packageJSON: IExtensionManifest }[] = [];
for (const extensionPath of ['vscode-test-resolver', 'github-authentication']) {
const packageJSON = JSON.parse((await fsp.readFile(FileAccess.asFileUri(`${builtinExtensionsPath}/${extensionPath}/package.json`).fsPath)).toString());
bundledExtensions.push({ extensionPath, packageJSON });
}
values['WORKBENCH_BUILTIN_EXTENSIONS'] = asJSON(bundledExtensions);
}
let data;
try {
@ -351,7 +365,7 @@ export class WebClientServer {
'default-src \'self\';',
'img-src \'self\' https: data: blob:;',
'media-src \'self\';',
`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
`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
'child-src \'self\';',
`frame-src 'self' https://*.vscode-cdn.net data:;`,
'worker-src \'self\' data: blob:;',

View file

@ -738,7 +738,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
}
protected _doCreateExtensionHostManager(extensionHost: IExtensionHost, initialActivationEvents: string[]): IExtensionHostManager {
return createExtensionHostManager(this._instantiationService, extensionHost, initialActivationEvents, this._acquireInternalAPI());
return createExtensionHostManager(this._instantiationService, extensionHost, initialActivationEvents, this._acquireInternalAPI(extensionHost));
}
private _onExtensionHostCrashOrExit(extensionHost: IExtensionHostManager, code: number, signal: string | null): void {
@ -1070,13 +1070,13 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
//#region Called by extension host
private _acquireInternalAPI(): IInternalExtensionService {
private _acquireInternalAPI(extensionHost: IExtensionHost): IInternalExtensionService {
return {
_activateById: (extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void> => {
return this._activateById(extensionId, reason);
},
_onWillActivateExtension: (extensionId: ExtensionIdentifier): void => {
return this._onWillActivateExtension(extensionId);
return this._onWillActivateExtension(extensionId, extensionHost.runningLocation);
},
_onDidActivateExtension: (extensionId: ExtensionIdentifier, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationReason: ExtensionActivationReason): void => {
return this._onDidActivateExtension(extensionId, codeLoadingTime, activateCallTime, activateResolvedTime, activationReason);
@ -1100,7 +1100,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
}
}
private _onWillActivateExtension(extensionId: ExtensionIdentifier): void {
private _onWillActivateExtension(extensionId: ExtensionIdentifier, runningLocation: ExtensionRunningLocation): void {
this._runningLocations.set(extensionId, runningLocation);
const extensionStatus = this._getOrCreateExtensionStatus(extensionId);
extensionStatus.onWillActivate();
}

View file

@ -38,6 +38,10 @@ export class ExtensionRunningLocationTracker {
@IExtensionManifestPropertiesService private readonly _extensionManifestPropertiesService: IExtensionManifestPropertiesService,
) { }
public set(extensionId: ExtensionIdentifier, runningLocation: ExtensionRunningLocation) {
this._runningLocation.set(extensionId, runningLocation);
}
public readExtensionKinds(extensionDescription: IExtensionDescription): ExtensionKind[] {
if (extensionDescription.isUnderDevelopment && this._environmentService.extensionDevelopmentKind) {
return this._environmentService.extensionDevelopmentKind;
@ -196,10 +200,14 @@ export class ExtensionRunningLocationTracker {
}
public computeRunningLocation(localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[], isInitialAllocation: boolean): ExtensionIdentifierMap<ExtensionRunningLocation | null> {
return this._doComputeRunningLocation(localExtensions, remoteExtensions, isInitialAllocation).runningLocation;
return this._doComputeRunningLocation(this._runningLocation, localExtensions, remoteExtensions, isInitialAllocation).runningLocation;
}
private _doComputeRunningLocation(localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[], isInitialAllocation: boolean): { runningLocation: ExtensionIdentifierMap<ExtensionRunningLocation | null>; maxLocalProcessAffinity: number; maxLocalWebWorkerAffinity: number } {
private _doComputeRunningLocation(existingRunningLocation: ExtensionIdentifierMap<ExtensionRunningLocation | null>, localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[], isInitialAllocation: boolean): { runningLocation: ExtensionIdentifierMap<ExtensionRunningLocation | null>; maxLocalProcessAffinity: number; maxLocalWebWorkerAffinity: number } {
// Skip extensions that have an existing running location
localExtensions = localExtensions.filter(extension => !existingRunningLocation.has(extension.identifier));
remoteExtensions = remoteExtensions.filter(extension => !existingRunningLocation.has(extension.identifier));
const extensionHostKinds = determineExtensionHostKinds(
localExtensions,
remoteExtensions,
@ -247,11 +255,18 @@ export class ExtensionRunningLocationTracker {
result.set(extension.identifier, new LocalWebWorkerRunningLocation(affinity));
}
// Add extensions that already have an existing running location
for (const [extensionIdKey, runningLocation] of existingRunningLocation) {
if (runningLocation) {
result.set(extensionIdKey, runningLocation);
}
}
return { runningLocation: result, maxLocalProcessAffinity: maxAffinity, maxLocalWebWorkerAffinity: maxLocalWebWorkerAffinity };
}
public initializeRunningLocation(localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[]): void {
const { runningLocation, maxLocalProcessAffinity, maxLocalWebWorkerAffinity } = this._doComputeRunningLocation(localExtensions, remoteExtensions, true);
const { runningLocation, maxLocalProcessAffinity, maxLocalWebWorkerAffinity } = this._doComputeRunningLocation(this._runningLocation, localExtensions, remoteExtensions, true);
this._runningLocation = runningLocation;
this._maxLocalProcessAffinity = maxLocalProcessAffinity;
this._maxLocalWebWorkerAffinity = maxLocalWebWorkerAffinity;