Skip to content

Commit

Permalink
fix #6228: don't block web socket with many plugins
Browse files Browse the repository at this point in the history
- use permessage-deflate to compress any ws message greater than 256kb
- remove raw package data from plugin metadata
  - frontend plugins don't have access to it anymore
  - backend plugins recover it from the disk
- install contributions info only to process
metadata in the frontend
- install dependencies info only to process metadata on the backend
- initialize debugger contributions from metadata, instead of sending it first to the plugin host and then back to the frontend

Signed-off-by: Anton Kosiakov <anton.kosyakov@typefox.io>
  • Loading branch information
akosyakov committed Sep 24, 2019
1 parent 2220b7b commit dd1d2a0
Show file tree
Hide file tree
Showing 28 changed files with 586 additions and 364 deletions.
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"vscode-languageserver-types": "^3.15.0-next",
"vscode-uri": "^1.0.8",
"vscode-ws-jsonrpc": "^0.1.1",
"ws": "^5.2.2",
"ws": "^7.1.2",
"yargs": "^11.1.0"
},
"publishConfig": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { JsonRpcProxyFactory, ConnectionHandler, JsonRpcConnectionHandler, JsonR

export type BindFrontendService = <T extends object>(path: string, serviceIdentifier: interfaces.ServiceIdentifier<T>) => interfaces.BindingWhenOnSyntax<T>;
export type BindBackendService = <T extends object, C extends object = object>(
path: string, serviceIdentifier: interfaces.ServiceIdentifier<T>, onActivation?: (service: T, proxy: JsonRpcProxy<C>) => T
path: string, serviceIdentifier: interfaces.ServiceIdentifier<T>, onActivation?: (service: T, proxy: JsonRpcProxy<C>, context: interfaces.Context) => T
) => void;
export type ConnectionContainerModuleCallBack = (registry: {
bind: interfaces.Bind
Expand Down Expand Up @@ -86,7 +86,7 @@ export const ConnectionContainerModule: symbol & { create(callback: ConnectionCo
bind(ConnectionHandler).toDynamicValue(context =>
new JsonRpcConnectionHandler<any>(path, proxy => {
const service = context.container.get(serviceIdentifier);
return onActivation ? onActivation(service, proxy) : service;
return onActivation ? onActivation(service, proxy, context) : service;
})
).inSingletonScope();
};
Expand Down
5 changes: 4 additions & 1 deletion packages/core/src/node/messaging/messaging-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,10 @@ export class MessagingContribution implements BackendApplicationContribution, Me
onStart(server: http.Server | https.Server): void {
const wss = new ws.Server({
server,
perMessageDeflate: false
perMessageDeflate: {
// don't compress if a message is less than 256kb
threshold: 256 * 1024
}
});
interface CheckAliveWS extends ws {
alive: boolean;
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-dev/src/node/hosted-plugin-reader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class HostedPluginReader implements BackendApplicationContribution {
protected deployerHandler: HostedPluginDeployerHandler;

async initialize(): Promise<void> {
this.pluginReader.doGetPluginMetadata(process.env.HOSTED_PLUGIN)
this.pluginReader.doGetPluginMetadata(process.env.HOSTED_PLUGIN, {})
.then(this.hostedPlugin.resolve.bind(this.hostedPlugin));

const pluginPath = process.env.HOSTED_PLUGIN;
Expand Down
3 changes: 2 additions & 1 deletion packages/plugin-ext-vscode/src/node/plugin-vscode-init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ export const doInitialization: BackendInitializationFn = (apiFactory: PluginAPIF
vscode.commands.registerCommand = function (command: theia.CommandDescription | string, handler?: <T>(...args: any[]) => T | Thenable<T>, thisArg?: any): any {
// use of the ID when registering commands
if (typeof command === 'string') {
const commands = plugin.model.contributes && plugin.model.contributes.commands;
const rawCommands = plugin.rawModel.contributes && plugin.rawModel.contributes.commands;
const commands = rawCommands ? Array.isArray(rawCommands) ? rawCommands : [rawCommands] : undefined;
if (handler && commands && commands.some(item => item.command === command)) {
return vscode.commands.registerHandler(command, handler, thisArg);
}
Expand Down
17 changes: 12 additions & 5 deletions packages/plugin-ext-vscode/src/node/scanner-vscode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
********************************************************************************/

import { injectable } from 'inversify';
import { PluginScanner, PluginEngine, PluginPackage, PluginModel, PluginLifecycle } from '@theia/plugin-ext';
import { PluginScanner, PluginEngine, PluginPackage, PluginModel, PluginLifecycle, PluginModelOptions } from '@theia/plugin-ext';
import { TheiaPluginScanner } from '@theia/plugin-ext/lib/hosted/node/scanners/scanner-theia';

@injectable()
Expand All @@ -27,13 +27,14 @@ export class VsCodePluginScanner extends TheiaPluginScanner implements PluginSca
return this.VSCODE_TYPE;
}

getModel(plugin: PluginPackage): PluginModel {
getModel(plugin: PluginPackage, options: PluginModelOptions): PluginModel {
// translate vscode builtins, as they are published with a prefix. See https://github.com/theia-ide/vscode-builtin-extensions/blob/master/src/republish.js#L50
const built_prefix = '@theia/vscode-builtin-';
if (plugin && plugin.name && plugin.name.startsWith(built_prefix)) {
plugin.name = plugin.name.substr(built_prefix.length);
}
const result: PluginModel = {
packagePath: plugin.packagePath,
// see id definition: https://github.com/microsoft/vscode/blob/15916055fe0cb9411a5f36119b3b012458fe0a1d/src/vs/platform/extensions/common/extensions.ts#L167-L169
id: `${plugin.publisher.toLowerCase()}.${plugin.name.toLowerCase()}`,
name: plugin.name,
Expand All @@ -47,10 +48,16 @@ export class VsCodePluginScanner extends TheiaPluginScanner implements PluginSca
},
entryPoint: {
backend: plugin.main
},
extensionDependencies: this.getDeployableDependencies(plugin.extensionDependencies || [])
}
};
result.contributes = this.readContributions(plugin);
if (options.contributions) {
result.activationEvents = plugin.activationEvents;
result.contributes = this.readContributions(plugin);
}
if (options.dependencies) {
result.rawExtensionDependencies = plugin.extensionDependencies;
result.extensionDependencies = this.getDeployableDependencies(plugin.extensionDependencies || []);
}
return result;
}

Expand Down
46 changes: 28 additions & 18 deletions packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ import {
import { ExtPluginApi } from './plugin-ext-api-contribution';
import { KeysToAnyValues, KeysToKeysToAnyValue } from './types';
import { CancellationToken, Progress, ProgressOptions } from '@theia/plugin';
import { IJSONSchema, IJSONSchemaSnippet } from '@theia/core/lib/common/json-schema';
import { DebuggerDescription } from '@theia/debug/lib/common/debug-service';
import { DebugProtocol } from 'vscode-debugprotocol';
import { SymbolInformation } from 'vscode-languageserver-types';
Expand All @@ -76,16 +75,6 @@ import { MaybePromise } from '@theia/core/lib/common/types';
import { QuickOpenItem, QuickOpenItemOptions } from '@theia/core/lib/common/quick-open-model';
import { QuickTitleButton } from '@theia/core/lib/common/quick-open-model';

export interface PluginInitData {
plugins: PluginMetadata[];
preferences: PreferenceData;
globalState: KeysToKeysToAnyValue;
workspaceState: KeysToKeysToAnyValue;
env: EnvInit;
extApi?: ExtPluginApi[];
activationEvents: string[]
}

export interface PreferenceData {
[scope: number]: any;
}
Expand All @@ -100,7 +89,7 @@ export interface Plugin {

export interface ConfigStorage {
hostLogPath: string;
hostStoragePath: string,
hostStoragePath?: string,
}

export interface EnvInit {
Expand Down Expand Up @@ -142,6 +131,7 @@ export const emptyPlugin: Plugin = {
type: 'empty',
version: 'empty'
},
packagePath: 'empty',
entryPoint: {

}
Expand All @@ -162,12 +152,35 @@ export const emptyPlugin: Plugin = {
}
};

export interface PluginManagerInitializeParams {
preferences: PreferenceData
globalState: KeysToKeysToAnyValue
workspaceState: KeysToKeysToAnyValue
env: EnvInit
extApi?: ExtPluginApi[]
}

export interface PluginManagerStartParams {
plugins: PluginMetadata[]
configStorage: ConfigStorage
activationEvents: string[]
}

export interface PluginManagerExt {
$stop(pluginId?: string): PromiseLike<void>;

$init(pluginInit: PluginInitData, configStorage: ConfigStorage): PromiseLike<void>;
/** initialize the manager, should be called only once */
$initiliaze(params: PluginManagerInitializeParams): Promise<void>;

/** load and activate plugins */
$start(params: PluginManagerStartParams): Promise<void>;

/** deactivate the plugin */
$stop(pluginId: string): Promise<void>;

/** deactivate all plugins */
$stop(): Promise<void>;

$updateStoragePath(path: string | undefined): PromiseLike<void>;
$updateStoragePath(path: string | undefined): Promise<void>;

$activateByEvent(event: string): Promise<void>;
}
Expand Down Expand Up @@ -1236,9 +1249,6 @@ export interface DebugExt {
$sessionDidChange(sessionId: string | undefined): void;
$provideDebugConfigurations(debugType: string, workspaceFolder: string | undefined): Promise<theia.DebugConfiguration[]>;
$resolveDebugConfigurations(debugConfiguration: theia.DebugConfiguration, workspaceFolder: string | undefined): Promise<theia.DebugConfiguration | undefined>;
$getSupportedLanguages(debugType: string): Promise<string[]>;
$getSchemaAttributes(debugType: string): Promise<IJSONSchema[]>;
$getConfigurationSnippets(debugType: string): Promise<IJSONSchemaSnippet[]>;
$createDebugSession(debugConfiguration: theia.DebugConfiguration): Promise<string>;
$terminateDebugSession(sessionId: string): Promise<void>;
$getTerminalCreationOptions(debugType: string): Promise<TerminalOptionsExt | undefined>;
Expand Down
39 changes: 31 additions & 8 deletions packages/plugin-ext/src/common/plugin-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,13 @@ export interface PluginProblemPatternContribution extends ProblemPatternContribu
name: string;
}

export interface PluginModelOptions {
/** whether to load contributions info */
contributions?: boolean
/** whether to load dependencies info */
dependencies?: boolean
}

export const PluginScanner = Symbol('PluginScanner');
/**
* This scanner process package.json object and returns plugin metadata objects.
Expand All @@ -220,7 +227,7 @@ export interface PluginScanner {
* @param {PluginPackage} plugin
* @returns {PluginModel}
*/
getModel(plugin: PluginPackage): PluginModel;
getModel(plugin: PluginPackage, options: PluginModelOptions): PluginModel;

/**
* Creates plugin's lifecycle.
Expand Down Expand Up @@ -378,12 +385,25 @@ export interface PluginModel {
frontend?: string;
backend?: string;
};
packagePath: string;

// frontend contributions info -- start
contributes?: PluginContribution;
activationEvents?: string[];
// frontend contributions info -- end

// backend dependencies info -- start
/**
* The deployable form of extensionDependencies from package.json,
* i.e. not `publisher.name`, but `vscode:extension/publisher.name`.
* The deployable form of `rawExtensionDependencies`,
* e.g. not `publisher.name`, but `vscode:extension/publisher.name`.
*/
extensionDependencies?: string[];
/**
* The extension dependencies as they defined in plugin package.json,
* e.g. in `publisher.name` form.
*/
rawExtensionDependencies?: string[];
// backend dependencies info -- end
}

/**
Expand Down Expand Up @@ -583,7 +603,6 @@ export interface ExtensionContext {

export interface PluginMetadata {
host: string;
source: PluginPackage;
model: PluginModel;
lifecycle: PluginLifecycle;
}
Expand Down Expand Up @@ -615,15 +634,19 @@ export interface PluginDeployerHandler {
deployFrontendPlugins(frontendPlugins: PluginDeployerEntry[]): Promise<void>;
deployBackendPlugins(backendPlugins: PluginDeployerEntry[]): Promise<void>;

getPluginMetadata(pluginToBeInstalled: PluginDeployerEntry): Promise<PluginMetadata | undefined>
getPluginMetadata(pluginToBeInstalled: PluginDeployerEntry, options: PluginModelOptions): Promise<PluginMetadata | undefined>
}

export interface GetDeployedPluginsParams {
pluginIds: string[]
}

export const HostedPluginServer = Symbol('HostedPluginServer');
export interface HostedPluginServer extends JsonRpcServer<HostedPluginClient> {

getDeployedMetadata(): Promise<PluginMetadata[]>;
getDeployedFrontendMetadata(): Promise<PluginMetadata[]>;
getDeployedBackendMetadata(): Promise<PluginMetadata[]>;
getDeployedPluginIds(): Promise<string[]>;

getDeployedPlugins(params: GetDeployedPluginsParams): Promise<PluginMetadata[]>;

getExtPluginAPI(): Promise<ExtPluginApi[]>;

Expand Down
Loading

0 comments on commit dd1d2a0

Please sign in to comment.