-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Add upper limit for the program size to prevent tsserver from crashing #7486
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
Changes from 3 commits
4d3cff1
b155fa8
a3aa000
a6a466c
d4eb3b8
a839d93
225e3b4
c8e0b00
d7e1d34
cb46f16
74e3d7b
1b76294
5c9ce9e
94d44ad
d387050
4383f1a
e41b10b
3354436
550d912
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2620,7 +2620,10 @@ | |
"category": "Message", | ||
"code": 6112 | ||
}, | ||
|
||
"Disable the upper limit for the total file size of a project.": { | ||
"category": "Message", | ||
"code": 6113 | ||
}, | ||
"Variable '{0}' implicitly has an '{1}' type.": { | ||
"category": "Error", | ||
"code": 7005 | ||
|
@@ -2824,5 +2827,9 @@ | |
"Unknown typing option '{0}'.": { | ||
"category": "Error", | ||
"code": 17010 | ||
}, | ||
"Too many JavaScript files in the project. Use an exact 'files' list, or use the 'exclude' setting in project configuration to limit included source folders. The likely folder to exclude is '{0}'. To disable the project size limit, set the 'disableSizeLimit' compiler option to 'true'": { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. consider updating the error message to:
|
||
"category": "Error", | ||
"code": 17012 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -742,13 +742,41 @@ namespace ts { | |
(oldOptions.target !== options.target) || | ||
(oldOptions.noLib !== options.noLib) || | ||
(oldOptions.jsx !== options.jsx) || | ||
(oldOptions.allowJs !== options.allowJs)) { | ||
(oldOptions.allowJs !== options.allowJs) || | ||
(oldOptions.disableSizeLimit !== options.disableSizeLimit)) { | ||
oldProgram = undefined; | ||
} | ||
} | ||
|
||
if (!tryReuseStructureFromOldProgram()) { | ||
forEach(rootNames, name => processRootFile(name, /*isDefaultLib*/ false)); | ||
if (options.disableSizeLimit === true) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if (options.disableSizeLimit) |
||
forEach(rootNames, name => processRootFile(name, /*isDefaultLib*/ false)); | ||
} | ||
else { | ||
let programSize = 0; | ||
for (const name of rootNames) { | ||
const path = toPath(name, currentDirectory, getCanonicalFileName); | ||
if (programSize <= maxProgramSize) { | ||
processRootFile(name, /*isDefaultLib*/ false); | ||
const file = filesByName.get(path); | ||
if (!hasTypeScriptFileExtension(name) && file && file.text) { | ||
programSize += file.text.length; | ||
} | ||
} | ||
else { | ||
// If the program size limit was reached when processing a file, this file is | ||
// likely in the problematic folder than contains too many files | ||
const commonSourceDirectory = getCommonSourceDirectory(); | ||
let rootLevelDirectory = path.substring(0, Math.max(commonSourceDirectory.length, path.indexOf(directorySeparator, commonSourceDirectory.length))); | ||
if (rootLevelDirectory[rootLevelDirectory.length - 1] !== directorySeparator) { | ||
rootLevelDirectory += directorySeparator; | ||
} | ||
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Too_many_JavaScript_files_in_the_project_Use_an_exact_files_list_or_use_the_exclude_setting_in_project_configuration_to_limit_included_source_folders_The_likely_folder_to_exclude_is_0_To_disable_the_project_size_limit_set_the_disableSizeLimit_compiler_option_to_true, rootLevelDirectory)); | ||
break; | ||
} | ||
} | ||
} | ||
|
||
// Do not process the default library if: | ||
// - The '--noLib' flag is used. | ||
// - A 'no-default-lib' reference comment is encountered in | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2484,6 +2484,10 @@ namespace ts { | |
return forEach(supportedJavascriptExtensions, extension => fileExtensionIs(fileName, extension)); | ||
} | ||
|
||
export function hasTypeScriptFileExtension(fileName: string) { | ||
return forEach(supportedTypeScriptExtensions, extension => fileExtensionIs(fileName, extension)); | ||
} | ||
|
||
/** | ||
* Replace each instance of non-ascii characters by one, two, three, or four escape sequences | ||
* representing the UTF-8 encoding of the character, and return the expanded char code list. | ||
|
@@ -2866,4 +2870,6 @@ namespace ts { | |
export function isParameterPropertyDeclaration(node: ParameterDeclaration): boolean { | ||
return node.flags & NodeFlags.AccessibilityModifier && node.parent.kind === SyntaxKind.Constructor && isClassLike(node.parent.parent); | ||
} | ||
|
||
export const maxProgramSize = 20 * 1024 * 1024; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed the upper limit from 35M to 20M, as on my machine even 35M would still run out of memory |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1217,10 +1217,41 @@ namespace ts.server { | |
} | ||
else { | ||
const project = this.createProject(configFilename, projectOptions); | ||
let programSize = 0; | ||
|
||
// As the project openning might not be complete if there are too many files, | ||
// therefore to surface the diagnostics we need to make sure the given client file is opened. | ||
if (clientFileName) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i thought we said we did not need this anymore? is this not the case? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The file reading here doesn't have an upper limit, therefore it could be much more than 20M. In the repro case it is around 100M, and in testing I noticed server crashes without the limit. So I kept the limit here as well. |
||
if (this.host.fileExists(clientFileName)) { | ||
const currentClientFileInfo = this.openFile(clientFileName, /*openedByClient*/ true); | ||
project.addRoot(currentClientFileInfo); | ||
programSize += currentClientFileInfo.content.length; | ||
} | ||
else { | ||
return { errorMsg: "specified file " + clientFileName + " not found" }; | ||
} | ||
} | ||
|
||
for (const rootFilename of projectOptions.files) { | ||
if (rootFilename === clientFileName) { | ||
continue; | ||
} | ||
|
||
if (this.host.fileExists(rootFilename)) { | ||
const info = this.openFile(rootFilename, /*openedByClient*/ clientFileName == rootFilename); | ||
project.addRoot(info); | ||
if (projectOptions.compilerOptions.disableSizeLimit === true) { | ||
const info = this.openFile(rootFilename, /*openedByClient*/ false); | ||
project.addRoot(info); | ||
} | ||
else if (programSize <= maxProgramSize) { | ||
const info = this.openFile(rootFilename, /*openedByClient*/ false); | ||
project.addRoot(info); | ||
if (!hasTypeScriptFileExtension(rootFilename)) { | ||
programSize += info.content.length; | ||
} | ||
} | ||
else { | ||
break; | ||
} | ||
} | ||
else { | ||
return { errorMsg: "specified file " + rootFilename + " not found" }; | ||
|
@@ -1251,7 +1282,10 @@ namespace ts.server { | |
return error; | ||
} | ||
else { | ||
const oldFileNames = project.compilerService.host.roots.map(info => info.fileName); | ||
// if the project is too large, the root files might not have been all loaded if the total | ||
// program size reached the upper limit. In that case project.projectOptions.files should | ||
// be more precise. However this would only happen for configured project. | ||
const oldFileNames = project.projectOptions ? project.projectOptions.files : project.compilerService.host.roots.map(info => info.fileName); | ||
const newFileNames = projectOptions.files; | ||
const fileNamesToRemove = oldFileNames.filter(f => newFileNames.indexOf(f) < 0); | ||
const fileNamesToAdd = newFileNames.filter(f => oldFileNames.indexOf(f) < 0); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove the description, so that it does not show in the help.