Skip to content

Commit

Permalink
Merge pull request #43006 from Microsoft/rebornix/largefile
Browse files Browse the repository at this point in the history
Fix #42839. avoid ui freeze when file is larger than heap size limit.
  • Loading branch information
rebornix authored Feb 14, 2018
2 parents a8449c4 + 6729e2b commit 37ced1a
Show file tree
Hide file tree
Showing 14 changed files with 81 additions and 28 deletions.
23 changes: 22 additions & 1 deletion src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,21 @@ function touch(file) {
});
}

function resolveJSFlags() {
let jsFlags = [];
if (args['js-flags']) {
jsFlags.push(args['js-flags']);
}
if (args['max-memory'] && !/max_old_space_size=(\d+)/g.exec(args['js-flags'])) {
jsFlags.push(`--max_old_space_size=${args['max-memory']}`);
}
if (jsFlags.length > 0) {
return jsFlags.join(' ');
} else {
return null;
}
}

// Language tags are case insensitve however an amd loader is case sensitive
// To make this work on case preserving & insensitive FS we do the following:
// the language bundles have lower case language tags and we always lower case
Expand Down Expand Up @@ -441,7 +456,8 @@ let nodeCachedDataDir = getNodeCachedDataDir().then(function (value) {

// tell v8 to not be lazy when parsing JavaScript. Generally this makes startup slower
// but because we generate cached data it makes subsequent startups much faster
app.commandLine.appendSwitch('--js-flags', '--nolazy');
let existingJSFlags = resolveJSFlags();
app.commandLine.appendSwitch('--js-flags', existingJSFlags ? existingJSFlags + ' --nolazy' : '--nolazy');
}
return value;
});
Expand All @@ -454,6 +470,11 @@ userDefinedLocale.then((locale) => {
}
});

let jsFlags = resolveJSFlags();
if (jsFlags) {
app.commandLine.appendSwitch('--js-flags', jsFlags);
}

// Load our code once ready
app.once('ready', function () {
perf.mark('main:appReady');
Expand Down
7 changes: 7 additions & 0 deletions src/vs/code/node/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,13 @@ export async function main(argv: string[]): TPromise<any> {
});
}

if (args['js-flags']) {
const match = /max_old_space_size=(\d+)/g.exec(args['js-flags']);
if (match && !args['max-memory']) {
argv.push(`--max-memory=${match[1]}`);
}
}

const options = {
detached: true,
env
Expand Down
1 change: 1 addition & 0 deletions src/vs/platform/environment/common/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export interface ParsedArgs {
'disable-updates'?: string;
'disable-crash-reporter'?: string;
'skip-add-to-recently-opened'?: boolean;
'max-memory'?: number;
'file-write'?: boolean;
'file-chmod'?: boolean;
'upload-logs'?: string;
Expand Down
1 change: 1 addition & 0 deletions src/vs/platform/environment/node/argv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ const troubleshootingHelp: { [name: string]: string; } = {
'--inspect-brk-extensions': localize('inspect-brk-extensions', "Allow debugging and profiling of extensions with the extension host being paused after start. Check the developer tools for the connection uri."),
'--disable-gpu': localize('disableGPU', "Disable GPU hardware acceleration."),
'--upload-logs': localize('uploadLogs', "Uploads logs from current session to a secure endpoint."),
'--max-memory': localize('maxMemory', "Max memory size for a window (in Mbytes).")
};

export function formatOptions(options: { [name: string]: string; }, columns: number): string {
Expand Down
3 changes: 2 additions & 1 deletion src/vs/platform/files/common/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,8 @@ export enum FileOperationResult {
FILE_READ_ONLY,
FILE_PERMISSION_DENIED,
FILE_TOO_LARGE,
FILE_INVALID_PATH
FILE_INVALID_PATH,
FILE_EXCEED_MEMORY_LIMIT
}

export const AutoSaveConfiguration = {
Expand Down
7 changes: 6 additions & 1 deletion src/vs/platform/files/node/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,9 @@
const WIN32_MAX_FILE_SIZE = 300 * 1024 * 1024; // 300 MB
const GENERAL_MAX_FILE_SIZE = 16 * 1024 * 1024 * 1024; // 16 GB

export const MAX_FILE_SIZE = process.arch === 'ia32' ? WIN32_MAX_FILE_SIZE : GENERAL_MAX_FILE_SIZE;
// See https://github.com/v8/v8/blob/5918a23a3d571b9625e5cce246bdd5b46ff7cd8b/src/heap/heap.cc#L149
const WIN32_MAX_HEAP_SIZE = 700 * 1024 * 1024; // 700 MB
const GENERAL_MAX_HEAP_SIZE = 700 * 2 * 1024 * 1024; // 1400 MB

export const MAX_FILE_SIZE = process.arch === 'ia32' ? WIN32_MAX_FILE_SIZE : GENERAL_MAX_FILE_SIZE;
export const MAX_HEAP_SIZE = process.arch === 'ia32' ? WIN32_MAX_HEAP_SIZE : GENERAL_MAX_HEAP_SIZE;
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { Emitter } from 'vs/base/common/event';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { TestTextResourceConfigurationService, TestContextService, TestLifecycleService } from 'vs/workbench/test/workbenchTestServices';
import { TestTextResourceConfigurationService, TestContextService, TestLifecycleService, TestEnvironmentService } from 'vs/workbench/test/workbenchTestServices';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import URI from 'vs/base/common/uri';
import { testWorkspace } from 'vs/platform/workspace/test/common/testWorkspace';
Expand Down Expand Up @@ -261,7 +261,7 @@ suite('ExtensionsTipsService Test', () => {
const myWorkspace = testWorkspace(URI.from({ scheme: 'file', path: folderDir }));
workspaceService = new TestContextService(myWorkspace);
instantiationService.stub(IWorkspaceContextService, workspaceService);
instantiationService.stub(IFileService, new FileService(workspaceService, new TestTextResourceConfigurationService(), new TestConfigurationService(), new TestLifecycleService(), { disableWatcher: true }));
instantiationService.stub(IFileService, new FileService(workspaceService, TestEnvironmentService, new TestTextResourceConfigurationService(), new TestConfigurationService(), new TestLifecycleService(), { disableWatcher: true }));
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import Uri from 'vs/base/common/uri';
import { BackupFileService, BackupFilesModel } from 'vs/workbench/services/backup/node/backupFileService';
import { FileService } from 'vs/workbench/services/files/node/fileService';
import { TextModel, createTextBufferFactory } from 'vs/editor/common/model/textModel';
import { TestContextService, TestTextResourceConfigurationService, getRandomTestPath, TestLifecycleService } from 'vs/workbench/test/workbenchTestServices';
import { TestContextService, TestTextResourceConfigurationService, getRandomTestPath, TestLifecycleService, TestEnvironmentService } from 'vs/workbench/test/workbenchTestServices';
import { Workspace, toWorkspaceFolders } from 'vs/platform/workspace/common/workspace';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { DefaultEndOfLine } from 'vs/editor/common/model';
Expand All @@ -39,7 +39,7 @@ const untitledBackupPath = path.join(workspaceBackupPath, 'untitled', crypto.cre

class TestBackupFileService extends BackupFileService {
constructor(workspace: Uri, backupHome: string, workspacesJsonPath: string) {
const fileService = new FileService(new TestContextService(new Workspace(workspace.fsPath, workspace.fsPath, toWorkspaceFolders([{ path: workspace.fsPath }]))), new TestTextResourceConfigurationService(), new TestConfigurationService(), new TestLifecycleService(), { disableWatcher: true });
const fileService = new FileService(new TestContextService(new Workspace(workspace.fsPath, workspace.fsPath, toWorkspaceFolders([{ path: workspace.fsPath }]))), TestEnvironmentService, new TestTextResourceConfigurationService(), new TestConfigurationService(), new TestLifecycleService(), { disableWatcher: true });

super(workspaceBackupPath, fileService);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { parseArgs } from 'vs/platform/environment/node/argv';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import extfs = require('vs/base/node/extfs');
import { TestTextFileService, TestTextResourceConfigurationService, workbenchInstantiationService, TestLifecycleService } from 'vs/workbench/test/workbenchTestServices';
import { TestTextFileService, TestTextResourceConfigurationService, workbenchInstantiationService, TestLifecycleService, TestEnvironmentService } from 'vs/workbench/test/workbenchTestServices';
import uuid = require('vs/base/common/uuid');
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService';
Expand Down Expand Up @@ -102,7 +102,7 @@ suite('ConfigurationEditingService', () => {
instantiationService.stub(IWorkspaceContextService, workspaceService);
return workspaceService.initialize(noWorkspace ? {} as IWindowConfiguration : workspaceDir).then(() => {
instantiationService.stub(IConfigurationService, workspaceService);
instantiationService.stub(IFileService, new FileService(workspaceService, new TestTextResourceConfigurationService(), new TestConfigurationService(), new TestLifecycleService(), { disableWatcher: true }));
instantiationService.stub(IFileService, new FileService(workspaceService, TestEnvironmentService, new TestTextResourceConfigurationService(), new TestConfigurationService(), new TestLifecycleService(), { disableWatcher: true }));
instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService));
instantiationService.stub(ITextModelService, <ITextModelService>instantiationService.createInstance(TextModelResolverService));
testObject = instantiationService.createInstance(ConfigurationEditingService);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { ConfigurationEditingErrorCode } from 'vs/workbench/services/configurati
import { FileChangeType, FileChangesEvent, IFileService } from 'vs/platform/files/common/files';
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace';
import { ConfigurationTarget, IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { workbenchInstantiationService, TestTextResourceConfigurationService, TestTextFileService, TestLifecycleService } from 'vs/workbench/test/workbenchTestServices';
import { workbenchInstantiationService, TestTextResourceConfigurationService, TestTextFileService, TestLifecycleService, TestEnvironmentService } from 'vs/workbench/test/workbenchTestServices';
import { FileService } from 'vs/workbench/services/files/node/fileService';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
Expand Down Expand Up @@ -151,7 +151,7 @@ suite('WorkspaceContextService - Workspace', () => {

return workspaceService.initialize({ id: configPath, configPath }).then(() => {

instantiationService.stub(IFileService, new FileService(<IWorkspaceContextService>workspaceService, new TestTextResourceConfigurationService(), workspaceService, new TestLifecycleService(), { disableWatcher: true }));
instantiationService.stub(IFileService, new FileService(<IWorkspaceContextService>workspaceService, TestEnvironmentService, new TestTextResourceConfigurationService(), workspaceService, new TestLifecycleService(), { disableWatcher: true }));
instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService));
instantiationService.stub(ITextModelService, <ITextModelService>instantiationService.createInstance(TextModelResolverService));
workspaceService.setInstantiationService(instantiationService);
Expand Down Expand Up @@ -395,7 +395,7 @@ suite('WorkspaceService - Initialization', () => {
instantiationService.stub(IEnvironmentService, environmentService);

return workspaceService.initialize(<IWindowConfiguration>{}).then(() => {
instantiationService.stub(IFileService, new FileService(<IWorkspaceContextService>workspaceService, new TestTextResourceConfigurationService(), workspaceService, new TestLifecycleService(), { disableWatcher: true }));
instantiationService.stub(IFileService, new FileService(<IWorkspaceContextService>workspaceService, TestEnvironmentService, new TestTextResourceConfigurationService(), workspaceService, new TestLifecycleService(), { disableWatcher: true }));
instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService));
instantiationService.stub(ITextModelService, <ITextModelService>instantiationService.createInstance(TextModelResolverService));
workspaceService.setInstantiationService(instantiationService);
Expand Down Expand Up @@ -647,7 +647,7 @@ suite('WorkspaceConfigurationService - Folder', () => {
instantiationService.stub(IEnvironmentService, environmentService);

return workspaceService.initialize(folderDir).then(() => {
instantiationService.stub(IFileService, new FileService(<IWorkspaceContextService>workspaceService, new TestTextResourceConfigurationService(), workspaceService, new TestLifecycleService(), { disableWatcher: true }));
instantiationService.stub(IFileService, new FileService(<IWorkspaceContextService>workspaceService, TestEnvironmentService, new TestTextResourceConfigurationService(), workspaceService, new TestLifecycleService(), { disableWatcher: true }));
instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService));
instantiationService.stub(ITextModelService, <ITextModelService>instantiationService.createInstance(TextModelResolverService));
workspaceService.setInstantiationService(instantiationService);
Expand Down Expand Up @@ -942,7 +942,7 @@ suite('WorkspaceConfigurationService - Multiroot', () => {

return workspaceService.initialize({ id: configPath, configPath }).then(() => {

instantiationService.stub(IFileService, new FileService(<IWorkspaceContextService>workspaceService, new TestTextResourceConfigurationService(), workspaceService, new TestLifecycleService(), { disableWatcher: true }));
instantiationService.stub(IFileService, new FileService(<IWorkspaceContextService>workspaceService, TestEnvironmentService, new TestTextResourceConfigurationService(), workspaceService, new TestLifecycleService(), { disableWatcher: true }));
instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService));
instantiationService.stub(ITextModelService, <ITextModelService>instantiationService.createInstance(TextModelResolverService));
workspaceService.setInstantiationService(instantiationService);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export class FileService implements IFileService {
};

// create service
this.raw = new NodeFileService(contextService, textResourceConfigurationService, configurationService, lifecycleService, fileServiceConfig);
this.raw = new NodeFileService(contextService, environmentService, textResourceConfigurationService, configurationService, lifecycleService, fileServiceConfig);

// Listeners
this.registerListeners();
Expand Down
31 changes: 24 additions & 7 deletions src/vs/workbench/services/files/node/fileService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import os = require('os');
import crypto = require('crypto');
import assert = require('assert');
import { isParent, FileOperation, FileOperationEvent, IContent, IFileService, IResolveFileOptions, IResolveFileResult, IResolveContentOptions, IFileStat, IStreamContent, FileOperationError, FileOperationResult, IUpdateContentOptions, FileChangeType, IImportResult, FileChangesEvent, ICreateFileOptions, IContentData, ITextSnapshot } from 'vs/platform/files/common/files';
import { MAX_FILE_SIZE } from 'vs/platform/files/node/files';
import { MAX_FILE_SIZE, MAX_HEAP_SIZE } from 'vs/platform/files/node/files';
import { isEqualOrParent } from 'vs/base/common/paths';
import { ResourceMap } from 'vs/base/common/map';
import arrays = require('vs/base/common/arrays');
Expand All @@ -36,6 +36,7 @@ import Event, { Emitter } from 'vs/base/common/event';
import { FileWatcher as NsfwWatcherService } from 'vs/workbench/services/files/node/watcher/nsfw/watcherService';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { getBaseLabel } from 'vs/base/common/labels';
Expand Down Expand Up @@ -131,6 +132,7 @@ export class FileService implements IFileService {

constructor(
private contextService: IWorkspaceContextService,
private environmentService: IEnvironmentService,
private textResourceConfigurationService: ITextResourceConfigurationService,
private configurationService: IConfigurationService,
private lifecycleService: ILifecycleService,
Expand Down Expand Up @@ -316,12 +318,20 @@ export class FileService implements IFileService {
}

// Return early if file is too large to load
if (typeof stat.size === 'number' && stat.size > MAX_FILE_SIZE) {
return onStatError(new FileOperationError(
nls.localize('fileTooLargeError', "File too large to open"),
FileOperationResult.FILE_TOO_LARGE,
options
));
if (typeof stat.size === 'number') {
if (stat.size > Math.max(this.environmentService.args['max-memory'] * 1024 * 1024 || 0, MAX_HEAP_SIZE)) {
return onStatError(new FileOperationError(
nls.localize('fileTooLargeForHeapError', "File size exceeds window memory limit, please try to run code --max-memory=NEWSIZE"),
FileOperationResult.FILE_EXCEED_MEMORY_LIMIT
));
}

if (stat.size > MAX_FILE_SIZE) {
return onStatError(new FileOperationError(
nls.localize('fileTooLargeError', "File too large to open"),
FileOperationResult.FILE_TOO_LARGE
));
}
}

return void 0;
Expand Down Expand Up @@ -455,6 +465,13 @@ export class FileService implements IFileService {
currentPosition += bytesRead;
}

if (totalBytesRead > Math.max(this.environmentService.args['max-memory'] * 1024 * 1024 || 0, MAX_HEAP_SIZE)) {
finish(new FileOperationError(
nls.localize('fileTooLargeForHeapError', "File size exceeds window memory limit, please try to run code --max-memory=NEWSIZE"),
FileOperationResult.FILE_EXCEED_MEMORY_LIMIT
));
}

if (totalBytesRead > MAX_FILE_SIZE) {
// stop when reading too much
finish(new FileOperationError(
Expand Down
Loading

0 comments on commit 37ced1a

Please sign in to comment.