Skip to content

Commit ecd46d3

Browse files
committed
Handle OOM in Java language server
Add parameters to the java server to make lemminx crash when it runs out of memory. Detect when the java server shuts down due to running out of memory, display a message to the user that this happened, and don't attempt to restart the server. Closes #527 Signed-off-by: David Thompson <davthomp@redhat.com>
1 parent 475372e commit ecd46d3

File tree

7 files changed

+113
-8
lines changed

7 files changed

+113
-8
lines changed

docs/Troubleshooting.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,23 @@ You can kill the process by:
2121

2222
* on Windows OS: run `taskkill /F /PID ...` all instances
2323
* on other OS: run `kill -9 ...` all instances
24+
25+
### The Language Server Crashes Due to an Out Of Memory Error
26+
27+
If you are working with large XML files or referencing large schema files,
28+
it's likely that increasing the memory available to the language server will resolve this issue.
29+
30+
If you aren't working with large XML files, then it may be a memory leak.
31+
Please [file a issue](https://github.com/redhat-developer/vscode-xml/issues) with a description of what you were doing if this is the case.
32+
33+
#### How to increase the amount of memory available to the Language Server
34+
35+
If you are using the Java Language Server:
36+
1. Go to settings
37+
2. Navigate to the setting `xml.server.vmargs`
38+
3. Add `-Xmx 1G` to the setting string. This allows the the language server to use at most 1 gigabyte of memory.
39+
4. If the problem persists, you can increase the `1G` to `2G` or higher
40+
41+
If you are using the binary language server:
42+
1. By default, the language server will use up to 80% of your RAM.
43+
Increasing this amount likely won't help solve the problem.

package-lock.json

Lines changed: 17 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
],
6969
"devDependencies": {
7070
"@types/fs-extra": "^8.0.0",
71+
"@types/glob": "^7.1.4",
7172
"@types/node": "^10.14.16",
7273
"@types/vscode": "^1.37.0",
7374
"@types/yauzl": "^2.9.1",

src/client/clientErrorHandler.ts

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
import { window } from "vscode";
1+
import * as fs from "fs-extra";
2+
import * as path from "path";
3+
import { commands, window } from "vscode";
24
import { CloseAction, ErrorAction, ErrorHandler, Message } from "vscode-languageclient";
5+
import { ClientCommandConstants } from "../commands/commandConstants";
6+
import glob = require("glob");
37

48
/**
59
* An error handler that restarts the language server,
6-
* unless it has been restarted 5 times in the last 3 minutes
10+
* unless it has been restarted 5 times in the last 3 minutes,
11+
* or if it crashed due to an Out Of Memory Error
712
*
813
* Adapted from [vscode-java](https://github.com/redhat-developer/vscode-java)
914
*/
@@ -23,6 +28,11 @@ export class ClientErrorHandler implements ErrorHandler {
2328

2429
closed(): CloseAction {
2530
this.restarts.push(Date.now());
31+
const heapProfileGlob = new glob.GlobSync(`${path.resolve(__dirname, '..')}/java_*.hprof`);
32+
if (heapProfileGlob.found.length) {
33+
showOOMMessage();
34+
return CloseAction.DoNotRestart;
35+
}
2636
if (this.restarts.length < 5) {
2737
return CloseAction.Restart;
2838
} else {
@@ -37,3 +47,32 @@ export class ClientErrorHandler implements ErrorHandler {
3747
}
3848

3949
}
50+
51+
/**
52+
* Deletes all the heap dumps generated by Out Of Memory errors
53+
*
54+
* @returns when the heap dumps have been deleted
55+
*/
56+
export async function cleanUpHeapDumps(): Promise<void> {
57+
const heapProfileGlob = new glob.GlobSync(`${path.resolve(__dirname, '..')}/java_*.hprof`);
58+
for (let heapProfile of heapProfileGlob.found) {
59+
await fs.remove(heapProfile);
60+
}
61+
}
62+
63+
/**
64+
* Shows a message about the server crashing due to an out of memory issue
65+
*/
66+
async function showOOMMessage(): Promise<void> {
67+
const DOCS = 'More info...';
68+
const result = await window.showErrorMessage('The XML Language Server crashed due to an Out Of Memory Error, and will not be restarted. ', //
69+
DOCS);
70+
if (result === DOCS) {
71+
await commands.executeCommand(ClientCommandConstants.OPEN_DOCS,
72+
{
73+
page: 'Troubleshooting',
74+
section: 'the-language-server-crashes-due-to-an-out-of-memory-error'
75+
}
76+
);
77+
}
78+
}

src/extension.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,16 @@ import { ExtensionContext, extensions, languages } from "vscode";
1616
import { Executable, LanguageClient } from 'vscode-languageclient/node';
1717
import { XMLExtensionApi } from './api/xmlExtensionApi';
1818
import { getXmlExtensionApiImplementation } from './api/xmlExtensionApiImplementation';
19+
import { cleanUpHeapDumps } from './client/clientErrorHandler';
1920
import { getIndentationRules } from './client/indentation';
2021
import { startLanguageClient } from './client/xmlClient';
22+
import { registerClientOnlyCommands } from './commands/registerCommands';
2123
import { collectXmlJavaExtensions } from './plugin';
2224
import * as requirements from './server/requirements';
2325
import { prepareExecutable } from './server/serverStarter';
2426
import { ExternalXmlSettings } from "./settings/externalXmlSettings";
2527
import { getXMLConfiguration } from './settings/settings';
2628
import { Telemetry } from './telemetry';
27-
import { registerClientOnlyCommands } from './commands/registerCommands';
2829

2930
let languageClient: LanguageClient;
3031

@@ -52,6 +53,7 @@ export async function activate(context: ExtensionContext): Promise<XMLExtensionA
5253
storagePath = os.homedir() + "/.lemminx";
5354
}
5455
const logfile = path.resolve(storagePath + '/lemminx.log');
56+
await cleanUpHeapDumps();
5557

5658
const externalXmlSettings: ExternalXmlSettings = new ExternalXmlSettings();
5759

src/server/binary/binaryServerStarter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { getProxySettings, getProxySettingsAsEnvironmentVariables, ProxySettings
1313
import { getXMLConfiguration } from "../../settings/settings";
1414
import { Telemetry } from '../../telemetry';
1515
import { addTrustedHash, getTrustedHashes } from './binaryHashManager';
16-
const glob = require('glob');
16+
import glob = require('glob');
1717

1818
const HTTPS_PATTERN = /^https:\/\//;
1919
const JAR_ZIP_AND_HASH_REJECTOR = /(?:\.jar)|(?:\.zip)|(?:\.sha256)$/;
@@ -430,4 +430,4 @@ async function acceptBinaryDownloadResponse(response: http.IncomingMessage): Pro
430430

431431
async function openProxyDocumentation(): Promise<void> {
432432
await commands.executeCommand(ClientCommandConstants.OPEN_DOCS, { page: "Proxy" });
433-
}
433+
}

src/server/java/javaServerStarter.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,31 @@
11
import * as os from 'os';
22
import * as path from 'path';
3-
import { ExtensionContext, workspace } from 'vscode';
3+
import { ExtensionContext, window, workspace } from 'vscode';
44
import { Executable } from 'vscode-languageclient/node';
55
import { getProxySettings, getProxySettingsAsJVMArgs, jvmArgsContainsProxySettings, ProxySettings } from '../../settings/proxySettings';
66
import { getJavaagentFlag, getKey, getXMLConfiguration, IS_WORKSPACE_VMARGS_XML_ALLOWED, xmlServerVmargs } from '../../settings/settings';
77
import { RequirementsData } from '../requirements';
8-
const glob = require('glob');
8+
import glob = require('glob');
99

1010
declare var v8debug;
1111

1212
const DEBUG = (typeof v8debug === 'object') || startedInDebugMode();
1313

14+
/**
15+
* Argument that tells the program to crash when an OutOfMemoryError is raised
16+
*/
17+
const CRASH_ON_OOM = '-XX:+ExitOnOutOfMemoryError';
18+
19+
/**
20+
* Argument that tells the program to generate a heap dump file when an OutOfMemoryError is raised
21+
*/
22+
const HEAP_DUMP = '-XX:+HeapDumpOnOutOfMemoryError';
23+
24+
/**
25+
* Argument that tells the program where to generate the heap dump that is created when an OutOfMemoryError is raised and `HEAP_DUMP` has been passed
26+
*/
27+
const HEAP_DUMP_LOCATION = '-XX:HeapDumpPath=';
28+
1429
export async function prepareJavaExecutable(
1530
context: ExtensionContext,
1631
requirements: RequirementsData,
@@ -63,6 +78,18 @@ function prepareParams(requirements: RequirementsData, xmlJavaExtensions: string
6378
params.push(watchParentProcess + 'false');
6479
}
6580
}
81+
if (vmargs.indexOf(CRASH_ON_OOM) < 0) {
82+
params.push(CRASH_ON_OOM);
83+
}
84+
if (vmargs.indexOf(HEAP_DUMP) < 0) {
85+
params.push(HEAP_DUMP);
86+
}
87+
if (vmargs.indexOf(HEAP_DUMP_LOCATION) < 0) {
88+
params.push(`${HEAP_DUMP_LOCATION}${path.resolve(__dirname, '..')}`);
89+
} else {
90+
window.showWarningMessage('Heap dump location has been modified; if you are experiencing Out Of Memory crashes, vscode-xml won\'t be able to detect them');
91+
}
92+
6693
// "OpenJDK 64-Bit Server VM warning: Options -Xverify:none and -noverify
6794
// were deprecated in JDK 13 and will likely be removed in a future release."
6895
// so only add -noverify for older versions

0 commit comments

Comments
 (0)