mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 21:09:43 +00:00
Merge pull request #184239 from microsoft/alexd/busy-raven
Take into account already activated extensions when computing running locations
This commit is contained in:
commit
d10902d080
|
@ -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'
|
||||
}
|
||||
});
|
|
@ -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"
|
||||
},
|
||||
|
|
133
extensions/vscode-test-resolver/src/extension.browser.ts
Normal file
133
extensions/vscode-test-resolver/src/extension.browser.ts
Normal 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();
|
||||
}
|
||||
}
|
|
@ -4,6 +4,9 @@
|
|||
"outDir": "./out",
|
||||
"types": [
|
||||
"node"
|
||||
],
|
||||
"lib": [
|
||||
"WebWorker"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
|
|
|
@ -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 ----- */
|
||||
|
||||
|
|
|
@ -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:;',
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue