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

Make use of v8 cached data #16366

Merged
merged 7 commits into from
Dec 2, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/vs/code/electron-main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,8 +413,12 @@ function getShellEnvironment(): TPromise<platform.IProcessEnvironment> {
}

function createPaths(environmentService: IEnvironmentService): TPromise<any> {
const paths = [environmentService.appSettingsHome, environmentService.userProductHome, environmentService.extensionsPath];

const paths = [
environmentService.appSettingsHome,
environmentService.userProductHome,
environmentService.extensionsPath,
environmentService.nodeCachedDataDir
];
return TPromise.join(paths.map(p => mkdirp(p))) as TPromise<any>;
}

Expand Down
2 changes: 2 additions & 0 deletions src/vs/code/electron-main/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ export interface IWindowConfiguration extends ParsedArgs {
filesToOpen?: IPath[];
filesToCreate?: IPath[];
filesToDiff?: IPath[];

nodeCachedDataDir: string;
}

export enum ReadyState {
Expand Down
3 changes: 2 additions & 1 deletion src/vs/code/electron-main/windows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,7 @@ export class WindowsManager implements IWindowsMainService {
configuration.filesToOpen = filesToOpen;
configuration.filesToCreate = filesToCreate;
configuration.filesToDiff = filesToDiff;
configuration.nodeCachedDataDir = this.environmentService.isBuilt && this.environmentService.nodeCachedDataDir;

return configuration;
}
Expand Down Expand Up @@ -1227,4 +1228,4 @@ export class WindowsManager implements IWindowsMainService {
}, 10 /* delay to unwind callback stack (IPC) */);
}
}
}
}
109 changes: 76 additions & 33 deletions src/vs/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,20 @@ var AMDLoader;
if (!Array.isArray(options.nodeModules)) {
options.nodeModules = [];
}
if (typeof options.nodeCachedDataWriteDelay !== 'number' || options.nodeCachedDataWriteDelay < 0) {
options.nodeCachedDataWriteDelay = 1000 * 7;
}
if (typeof options.onNodeCachedDataError !== 'function') {
options.onNodeCachedDataError = function (err) {
if (err.errorCode === 'cachedDataRejected') {
console.warn('Rejected cached data from file: ' + err.path);
}
else if (err.errorCode === 'unlink' || err.errorCode === 'writeFile') {
console.error('Problems writing cached data file: ' + err.path);
console.error(err.detail);
}
};
}
return options;
};
ConfigurationOptionsUtil.mergeConfigurationOptions = function (overwrite, base) {
Expand Down Expand Up @@ -906,7 +920,6 @@ var AMDLoader;
this._queuedDefineCalls = [];
this._loadingScriptsCount = 0;
this._resolvedScriptPaths = {};
this._checksums = {};
}
ModuleManager._findRelevantLocationInStack = function (needle, stack) {
var normalize = function (str) { return str.replace(/\\/g, '/'); };
Expand Down Expand Up @@ -968,12 +981,6 @@ var AMDLoader;
ModuleManager.prototype.getLoaderEvents = function () {
return this.getRecorder().getEvents();
};
ModuleManager.prototype.recordChecksum = function (scriptSrc, checksum) {
this._checksums[scriptSrc] = checksum;
};
ModuleManager.prototype.getChecksums = function () {
return this._checksums;
};
/**
* Defines a module.
* @param id @see defineModule
Expand Down Expand Up @@ -1297,9 +1304,6 @@ var AMDLoader;
result.getStats = function () {
return _this.getLoaderEvents();
};
result.getChecksums = function () {
return _this.getChecksums();
};
result.__$__nodeRequire = global.nodeRequire;
return result;
};
Expand Down Expand Up @@ -1718,7 +1722,6 @@ var AMDLoader;
NodeScriptLoader.prototype.load = function (scriptSrc, callback, errorback, recorder) {
var _this = this;
var opts = this._moduleManager.getConfigurationOptions();
var checksum = opts.checksum || false;
var nodeRequire = (opts.nodeRequire || global.nodeRequire);
var nodeInstrumenter = (opts.nodeInstrumenter || function (c) { return c; });
this._init(nodeRequire);
Expand All @@ -1745,15 +1748,6 @@ var AMDLoader;
errorback(err);
return;
}
if (checksum) {
var hash = _this._crypto
.createHash('md5')
.update(data, 'utf8')
.digest('base64')
.replace(/=+$/, '');
_this._moduleManager.recordChecksum(scriptSrc, hash);
}
recorder.record(LoaderEventType.NodeBeginEvaluatingScript, scriptSrc);
var vmScriptSrc = _this._path.normalize(scriptSrc);
// Make the script src friendly towards electron
if (isElectronRenderer) {
Expand All @@ -1771,19 +1765,74 @@ var AMDLoader;
contents = prefix + data + suffix;
}
contents = nodeInstrumenter(contents, vmScriptSrc);
var r;
if (/^v0\.12/.test(process.version)) {
r = _this._vm.runInThisContext(contents, { filename: vmScriptSrc });
if (!opts.nodeCachedDataDir) {
_this._loadAndEvalScript(scriptSrc, vmScriptSrc, contents, { filename: vmScriptSrc }, recorder);
callback();
}
else {
r = _this._vm.runInThisContext(contents, vmScriptSrc);
var cachedDataPath_1 = _this._getCachedDataPath(opts.nodeCachedDataDir, scriptSrc);
_this._fs.readFile(cachedDataPath_1, function (err, data) {
// create script options
var scriptOptions = {
filename: vmScriptSrc,
produceCachedData: typeof data === 'undefined',
cachedData: data
};
var script = _this._loadAndEvalScript(scriptSrc, vmScriptSrc, contents, scriptOptions, recorder);
callback();
// cached code after math
if (script.cachedDataRejected) {
// data rejected => delete cache file
opts.onNodeCachedDataError({
errorCode: 'cachedDataRejected',
path: cachedDataPath_1
});
NodeScriptLoader._runSoon(function () { return _this._fs.unlink(cachedDataPath_1, function (err) {
if (err) {
opts.onNodeCachedDataError({
errorCode: 'unlink',
path: cachedDataPath_1,
detail: err
});
}
}); }, opts.nodeCachedDataWriteDelay);
}
else if (script.cachedDataProduced) {
// data produced => write cache file
NodeScriptLoader._runSoon(function () { return _this._fs.writeFile(cachedDataPath_1, script.cachedData, function (err) {
if (err) {
opts.onNodeCachedDataError({
errorCode: 'writeFile',
path: cachedDataPath_1,
detail: err
});
}
}); }, opts.nodeCachedDataWriteDelay);
}
});
}
r.call(global, RequireFunc, DefineFunc, vmScriptSrc, _this._path.dirname(scriptSrc));
recorder.record(LoaderEventType.NodeEndEvaluatingScript, scriptSrc);
callback();
});
}
};
NodeScriptLoader.prototype._loadAndEvalScript = function (scriptSrc, vmScriptSrc, contents, options, recorder) {
// create script, run script
recorder.record(LoaderEventType.NodeBeginEvaluatingScript, scriptSrc);
var script = new this._vm.Script(contents, options);
var r = script.runInThisContext(options);
r.call(global, RequireFunc, DefineFunc, vmScriptSrc, this._path.dirname(scriptSrc));
// signal done
recorder.record(LoaderEventType.NodeEndEvaluatingScript, scriptSrc);
return script;
};
NodeScriptLoader.prototype._getCachedDataPath = function (baseDir, filename) {
var hash = this._crypto.createHash('md5').update(filename, 'utf8').digest('hex');
var basename = this._path.basename(filename).replace(/\.js$/, '');
return this._path.join(baseDir, hash + "-" + basename + ".code");
};
NodeScriptLoader._runSoon = function (callback, minTimeout) {
var timeout = minTimeout + Math.ceil(Math.random() * minTimeout);
setTimeout(callback, timeout);
};
NodeScriptLoader._BOM = 0xFEFF;
return NodeScriptLoader;
}());
Expand Down Expand Up @@ -1862,12 +1911,6 @@ var AMDLoader;
RequireFunc.getStats = function () {
return moduleManager.getLoaderEvents();
};
/**
* Non standard extension to fetch checksums
*/
RequireFunc.getChecksums = function () {
return moduleManager.getChecksums();
};
return RequireFunc;
}());
var global = _amdLoaderGlobal, hasPerformanceNow = (global.performance && typeof global.performance.now === 'function'), isWebWorker, isElectronRenderer, isElectronMain, isNode, scriptLoader, moduleManager, loaderAvailableTimestamp;
Expand Down
4 changes: 3 additions & 1 deletion src/vs/platform/environment/common/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,6 @@ export interface IEnvironmentService {

mainIPCHandle: string;
sharedIPCHandle: string;
}

nodeCachedDataDir: string;
}
5 changes: 4 additions & 1 deletion src/vs/platform/environment/node/environmentService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ export class EnvironmentService implements IEnvironmentService {
@memoize
get sharedIPCHandle(): string { return `${getIPCHandlePrefix()}-${pkg.version}-shared${getIPCHandleSuffix()}`; }

@memoize
get nodeCachedDataDir(): string { return path.join(this.userDataPath, 'CachedData', pkg.version); }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jrieken should we not use the commit ID of the built as a way to identify the backup? if we use the version, wouldn't we use old cached data each time we update insiders build?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We update the cached data then

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jrieken ok but that basically means each insider user now has the performance penalty to pay to get the cached data and write to disk instead of also having the performance gain on insiders?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, nevermind, since we update insiders nightly, the cache needs to be invalidated anyways.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - we could consider not enabling this for insiders at all. However, to get started and to collect feedback I'll keep it enabled.


constructor(private _args: ParsedArgs, private _execPath: string) { }
}

Expand All @@ -137,4 +140,4 @@ export function parseUserDataDir(args: ParsedArgs, process: NodeJS.Process) {
}
}
return path.resolve(paths.getDefaultUserDataPath(process.platform));
}
}
10 changes: 7 additions & 3 deletions src/vs/workbench/electron-browser/bootstrap/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,15 @@ function main() {
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,
recordStats: !!configuration.performance
recordStats: !!configuration.performance,
nodeCachedDataDir: configuration.nodeCachedDataDir,
onNodeCachedDataError: function (err) { nodeCachedDataErrors.push(err) },
});

if (nlsConfig.pseudo) {
Expand All @@ -166,7 +171,6 @@ function main() {
}

// Perf Counters
window.MonacoEnvironment = {};
const timers = window.MonacoEnvironment.timers = {
start: new Date(configuration.isInitialStartup ? configuration.perfStartTime : configuration.perfWindowLoadTime),
isInitialStartup: !!configuration.isInitialStartup,
Expand Down Expand Up @@ -194,4 +198,4 @@ function main() {
});
}

main();
main();
89 changes: 89 additions & 0 deletions src/vs/workbench/electron-browser/nodeCachedDataManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';

import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { onUnexpectedError } from 'vs/base/common/errors';
import { TPromise } from 'vs/base/common/winjs.base';
import { dirname, join } from 'vs/base/common/paths';
import { readdir, rimraf } from 'vs/base/node/pfs';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';


export class NodeCachedDataManager {

private _telemetryService: ITelemetryService;
private _environmentService: IEnvironmentService;
private _disposables: IDisposable[] = [];

constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IEnvironmentService environmentService: IEnvironmentService
) {
this._telemetryService = telemetryService;
this._environmentService = environmentService;

this._handleCachedDataErrors();
this._manageCachedDataSoon();
}

dispose(): void {
this._disposables = dispose(this._disposables);
}

private _handleCachedDataErrors(): void {
const onNodeCachedDataError = (err) => {
this._telemetryService.publicLog('nodeCachedData', { errorCode: err.errorCode, path: err.path });
};

// handle future and past errors
(<any>self).require.config({ onNodeCachedDataError }, true);
(<any[]>(<any>window).MonacoEnvironment.nodeCachedDataErrors).forEach(onNodeCachedDataError);
delete (<any>window).MonacoEnvironment.nodeCachedDataErrors;

// stop when being disposed
this._disposables.push({
dispose() {
(<any>self).require.config({ onNodeCachedDataError: undefined }, true);
}
});
}

private _manageCachedDataSoon(): void {
// Cached data is stored as user data in directories like `CachedData/1.8.0`.
// This function makes sure to delete cached data from previous versions,
// like`CachedData/1.7.2`.

const {nodeCachedDataDir} = this._environmentService;
if (!nodeCachedDataDir) {
return;
}

let handle = setTimeout(() => {
handle = undefined;

const nodeCachedDataBase = dirname(nodeCachedDataDir);

readdir(nodeCachedDataBase).then(entries => {

const deletes = entries.map(entry => {
const path = join(nodeCachedDataBase, entry);
if (path !== nodeCachedDataDir) {
return rimraf(path);
}
});

return TPromise.join(deletes);

}).done(undefined, onUnexpectedError);

}, 30 * 1000);

this._disposables.push({
dispose() { clearTimeout(handle); }
});
}
}
6 changes: 6 additions & 0 deletions src/vs/workbench/electron-browser/shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ import { MainThreadModeServiceImpl } from 'vs/editor/common/services/modeService
import { IModeService } from 'vs/editor/common/services/modeService';
import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { CrashReporter } from 'vs/workbench/electron-browser/crashReporter';
import { NodeCachedDataManager } from 'vs/workbench/electron-browser/nodeCachedDataManager';
import { IThemeService } from 'vs/workbench/services/themes/common/themeService';
import { ThemeService } from 'vs/workbench/services/themes/electron-browser/themeService';
import { getDelayedChannel } from 'vs/base/parts/ipc/common/ipc';
Expand Down Expand Up @@ -169,7 +170,12 @@ export class WorkbenchShell {
this.workbench = instantiationService.createInstance(Workbench, parent.getHTMLElement(), workbenchContainer.getHTMLElement(), this.workspace, this.options, serviceCollection);
this.workbench.startup({
onWorkbenchStarted: (customKeybindingsCount, restoreViewletDuration, restoreEditorsDuration) => {

// run workbench started logic
this.onWorkbenchStarted(customKeybindingsCount, restoreViewletDuration, restoreEditorsDuration);

// start cached data manager
instantiationService.createInstance(NodeCachedDataManager);
}
});

Expand Down