Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move shared process to a window #21876

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/vs/base/node/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import { createGunzip } from 'zlib';

export type Agent = any;

export interface IRawRequestFunction {
(options: http.RequestOptions, callback?: (res: http.IncomingMessage) => void): http.ClientRequest;
}

export interface IRequestOptions {
type?: string;
url?: string;
Expand All @@ -28,6 +32,7 @@ export interface IRequestOptions {
agent?: Agent;
followRedirects?: number;
strictSSL?: boolean;
getRawRequest?(options: IRequestOptions): IRawRequestFunction;
}

export interface IRequestContext {
Expand All @@ -44,12 +49,18 @@ export interface IRequestFunction {
(options: IRequestOptions): TPromise<IRequestContext>;
}

function getNodeRequest(options: IRequestOptions): IRawRequestFunction {
const endpoint = parseUrl(options.url);
return endpoint.protocol === 'https:' ? https.request : http.request;
}

export function request(options: IRequestOptions): TPromise<IRequestContext> {
let req: http.ClientRequest;

return new TPromise<IRequestContext>((c, e) => {
const endpoint = parseUrl(options.url);
const rawRequest = endpoint.protocol === 'https:' ? https.request : http.request;
const getRawRequest = options.getRawRequest || getNodeRequest;
const rawRequest = getRawRequest(options);
const opts: https.RequestOptions = {
hostname: endpoint.hostname,
port: endpoint.port ? parseInt(endpoint.port) : (endpoint.protocol === 'https:' ? 443 : 80),
Expand Down
2 changes: 1 addition & 1 deletion src/vs/code/buildfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ exports.collectModules= function() {
createModuleDescription('vs/code/electron-main/main', []),
createModuleDescription('vs/code/node/cli', []),
createModuleDescription('vs/code/node/cliProcessMain', ['vs/code/node/cli']),
createModuleDescription('vs/code/node/sharedProcessMain', [])
createModuleDescription('vs/code/electron-browser/sharedProcessMain', [])
];
};
32 changes: 32 additions & 0 deletions src/vs/code/electron-browser/sharedProcess.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8" />
</head>

<body class="monaco-shell vs-dark" aria-label="">
<script>
(function () {
try {
window.location.search.substring(1).split('&').forEach(p => {
var kv = p.split('=');
if (kv[0] === 'config' && kv[1]) {
let config = JSON.parse(decodeURIComponent(kv[1]));
window.document.body.className = 'monaco-shell ' + config.baseTheme;
}
});
} catch (error) {
console.error(error);
}
})();
</script>

Shared Process
</body>

<!-- Startup via index.js -->
<script src="sharedProcess.js"></script>

</html>
102 changes: 102 additions & 0 deletions src/vs/code/electron-browser/sharedProcess.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

// Warning: Do not use the `let` declarator in this file, it breaks our minification

'use strict';

/*global window,document,define*/

const path = require('path');
const electron = require('electron');
const remote = electron.remote;
const ipc = electron.ipcRenderer;

function assign(destination, source) {
return Object.keys(source)
.reduce(function (r, key) { r[key] = source[key]; return r; }, destination);
}

function parseURLQueryArgs() {
const search = window.location.search || '';

return search.split(/[?&]/)
.filter(function (param) { return !!param; })
.map(function (param) { return param.split('='); })
.filter(function (param) { return param.length === 2; })
.reduce(function (r, param) { r[param[0]] = decodeURIComponent(param[1]); return r; }, {});
}

function createScript(src, onload) {
const script = document.createElement('script');
script.src = src;
script.addEventListener('load', onload);

const head = document.getElementsByTagName('head')[0];
head.insertBefore(script, head.lastChild);
}

function uriFromPath(_path) {
var pathName = path.resolve(_path).replace(/\\/g, '/');
if (pathName.length > 0 && pathName.charAt(0) !== '/') {
pathName = '/' + pathName;
}

return encodeURI('file://' + pathName);
}

function main() {
const args = parseURLQueryArgs();
const configuration = JSON.parse(args['config'] || '{}') || {};

// Get the nls configuration into the process.env as early as possible.
var nlsConfig = { availableLanguages: {} };
const config = process.env['VSCODE_NLS_CONFIG'];
if (config) {
process.env['VSCODE_NLS_CONFIG'] = config;
try {
nlsConfig = JSON.parse(config);
} catch (e) { /*noop*/ }
}

var locale = nlsConfig.availableLanguages['*'] || 'en';
if (locale === 'zh-tw') {
locale = 'zh-Hant';
} else if (locale === 'zh-cn') {
locale = 'zh-Hans';
}

window.document.documentElement.setAttribute('lang', locale);

// Load the loader and start loading the workbench
const rootUrl = uriFromPath(configuration.appRoot) + '/out';

// In the bundled version the nls plugin is packaged with the loader so the NLS Plugins
// loads as soon as the loader loads. To be able to have pseudo translation
createScript(rootUrl + '/vs/loader.js', function () {
define('fs', ['original-fs'], function (originalFS) { return originalFS; }); // replace the patched electron fs with the original node fs for all AMD code

window.MonacoEnvironment = {};

const nodeCachedDataErrors = window.MonacoEnvironment.nodeCachedDataErrors = [];
require.config({
baseUrl: rootUrl,
'vs/nls': nlsConfig,
nodeCachedDataDir: configuration.nodeCachedDataDir,
onNodeCachedDataError: function (err) { nodeCachedDataErrors.push(err) },
nodeModules: [/*BUILD->INSERT_NODE_MODULES*/]
});

if (nlsConfig.pseudo) {
require(['vs/nls'], function (nlsPlugin) {
nlsPlugin.setPseudoTranslation(nlsConfig.pseudo);
});
}

require(['vs/code/electron-browser/sharedProcessMain'], function () { });
});
}

main();
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { ExtensionManagementChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc';
import { IExtensionManagementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
Expand All @@ -21,39 +21,22 @@ import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/ex
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
import { IRequestService } from 'vs/platform/request/node/request';
import { RequestService } from 'vs/platform/request/node/requestService';
import { RequestService } from 'vs/platform/request/electron-browser/requestService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { combinedAppender, NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc';
import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
import { ISharedProcessInitData } from './sharedProcess';
import { IChoiceService } from 'vs/platform/message/common/message';
import { ChoiceChannelClient } from 'vs/platform/message/common/messageIpc';
import { IWindowsService } from 'vs/platform/windows/common/windows';
import { WindowsChannelClient } from 'vs/platform/windows/common/windowsIpc';
import { ActiveWindowManager } from 'vs/code/common/windows';
import { ipcRenderer } from 'electron';

function quit(err?: Error) {
if (err) {
console.error(err.stack || err);
}

process.exit(err ? 1 : 0);
}

/**
* Plan B is to kill oneself if one's parent dies. Much drama.
*/
function setupPlanB(parentPid: number): void {
setInterval(function () {
try {
process.kill(parentPid, 0); // throws an exception if the main process doesn't exist anymore.
} catch (e) {
process.exit();
}
}, 5000);
interface ISharedProcessInitData {
args: ParsedArgs;
}

const eventPrefix = 'monacoworkbench';
Expand Down Expand Up @@ -161,14 +144,11 @@ function setupIPC(hook: string): TPromise<Server> {

function handshake(): TPromise<ISharedProcessInitData> {
return new TPromise<ISharedProcessInitData>((c, e) => {
process.once('message', c);
process.once('error', e);
process.send('hello');
ipcRenderer.once('handshake', (_, r) => c(r));
ipcRenderer.send('handshake');
});
}

setupIPC(process.env['VSCODE_SHARED_IPC_HOOK'])
.then(server => handshake()
.then(data => main(server, data))
.then(() => setupPlanB(process.env['VSCODE_PID']))
.done(null, quit));
.then(data => main(server, data)));
21 changes: 4 additions & 17 deletions src/vs/code/electron-main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@ import { Server, serve, connect } from 'vs/base/parts/ipc/node/ipc.net';
import { TPromise } from 'vs/base/common/winjs.base';
import { AskpassChannel } from 'vs/workbench/parts/git/common/gitIpc';
import { GitAskpassService } from 'vs/workbench/parts/git/electron-main/askpassService';
import { spawnSharedProcess } from 'vs/code/node/sharedProcess';
import { spawnSharedProcess } from 'vs/code/electron-main/sharedProcess';
import { Mutex } from 'windows-mutex';
import { IDisposable } from 'vs/base/common/lifecycle';
import { LaunchService, ILaunchChannel, LaunchChannel, LaunchChannelClient, ILaunchService } from './launch';
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
Expand All @@ -45,7 +44,7 @@ import { EnvironmentService } from 'vs/platform/environment/node/environmentServ
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
import { IRequestService } from 'vs/platform/request/node/request';
import { RequestService } from 'vs/platform/request/node/requestService';
import { RequestService } from 'vs/platform/request/electron-main/requestService';
import { IURLService } from 'vs/platform/url/common/url';
import { URLChannel } from 'vs/platform/url/common/urlIpc';
import { URLService } from 'vs/platform/url/electron-main/urlService';
Expand Down Expand Up @@ -144,17 +143,9 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platfo

// Spawn shared process
const initData = { args: environmentService.args };
const options = {
allowOutput: !environmentService.isBuilt || environmentService.verbose,
debugPort: environmentService.isBuilt ? null : 5871
};

let sharedProcessDisposable: IDisposable;

const sharedProcess = spawnSharedProcess(initData, options).then(disposable => {
sharedProcessDisposable = disposable;
return connect(environmentService.sharedIPCHandle, 'main');
});
const sharedProcess = spawnSharedProcess(initData, environmentService.appRoot, environmentService.nodeCachedDataDir)
.then(disposable => connect(environmentService.sharedIPCHandle, 'main'));

// Create a new service collection, because the telemetry service
// requires a connection to shared process, which was only established
Expand Down Expand Up @@ -220,10 +211,6 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platfo
mainIpcServer = null;
}

if (sharedProcessDisposable) {
sharedProcessDisposable.dispose();
}

if (windowsMutex) {
windowsMutex.release();
}
Expand Down
38 changes: 38 additions & 0 deletions src/vs/code/electron-main/sharedProcess.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { assign } from 'vs/base/common/objects';
import { ParsedArgs } from 'vs/platform/environment/common/environment';
import { TPromise } from 'vs/base/common/winjs.base';
import { BrowserWindow, ipcMain } from 'electron';

export interface ISharedProcessInitData {
args: ParsedArgs;
}

export function spawnSharedProcess(initData: ISharedProcessInitData, appRoot: string, nodeCachedDataDir: string): TPromise<void> {
const window = new BrowserWindow();
const config = assign({ appRoot, nodeCachedDataDir });

const url = `${require.toUrl('vs/code/electron-browser/sharedProcess.html')}?config=${encodeURIComponent(JSON.stringify(config))}`;
window.loadURL(url);
window.webContents.openDevTools();
// window.hide();

// Prevent the window from dying
window.on('close', e => {
if (window.isVisible()) {
e.preventDefault();
window.hide();
}
});

return new TPromise<void>((c, e) => {
ipcMain.once('handshake', ({ sender }) => {
sender.send('handshake', initData);
c(null);
});
});
}
Loading