forked from redhat-developer/vscode-xml
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathclientErrorHandler.ts
158 lines (144 loc) · 5.86 KB
/
clientErrorHandler.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
import * as fs from "fs-extra";
import { commands, ConfigurationTarget, ExtensionContext, window, workspace } from "vscode";
import { CloseAction, CloseHandlerResult, ErrorAction, ErrorHandler, ErrorHandlerResult, Message } from "vscode-languageclient";
import * as ClientCommandConstants from "../commands/clientCommandConstants";
import { HEAP_DUMP_LOCATION } from "../server/java/jvmArguments";
import * as Telemetry from "../telemetry";
import glob = require("glob");
import { totalmem } from "os";
/**
* An error handler that restarts the language server,
* unless it has been restarted 5 times in the last 10 minutes,
* or if it crashed due to an Out Of Memory Error
*
* Adapted from [vscode-java](https://github.com/redhat-developer/vscode-java)
*/
export class ClientErrorHandler implements ErrorHandler {
private restarts: number[];
private name: string;
private context: ExtensionContext;
private heapDumpFolder: string;
constructor(name: string, context: ExtensionContext) {
this.name = name;
this.restarts = [];
this.context = context;
this.heapDumpFolder = getHeapDumpFolderFromSettings() || context.globalStorageUri.fsPath;
}
error(_error: Error, _message: Message, _count: number): ErrorHandlerResult {
return {
action: ErrorAction.Continue
};
}
closed(): CloseHandlerResult {
return {
action: this.doClosed()
}
}
doClosed(): CloseAction {
this.restarts.push(Date.now());
const heapProfileGlob = new glob.GlobSync(`${this.heapDumpFolder}/java_*.hprof`);
if (heapProfileGlob.found.length) {
// Only clean heap dumps that are generated in the default location.
// The default location is the extension global storage
// This means that if users change the folder where the heap dumps are placed,
// then they will be able to read the heap dumps,
// since they aren't immediately deleted.
cleanUpHeapDumps(this.context);
Telemetry.sendTelemetry(Telemetry.JAVA_OOM_EVT, { 'jvm.xmx': getXmxFromSettings() });
showOOMMessage();
return CloseAction.DoNotRestart;
}
if (this.restarts.length < 5) {
return CloseAction.Restart;
} else {
const diff = this.restarts[this.restarts.length - 1] - this.restarts[0];
if (diff <= 10 * 60 * 1000) {
window.showErrorMessage(`The ${this.name} language server crashed 5 times in the last 10 minutes. The server will not be restarted.`);
return CloseAction.DoNotRestart;
}
this.restarts.shift();
return CloseAction.Restart;
}
}
}
/**
* Deletes all the heap dumps generated by Out Of Memory errors
*
* @returns when the heap dumps have been deleted
*/
export async function cleanUpHeapDumps(context: ExtensionContext): Promise<void> {
const heapProfileGlob = new glob.GlobSync(`${context.globalStorageUri.fsPath}/java_*.hprof`);
for (const heapProfile of heapProfileGlob.found) {
await fs.remove(heapProfile);
}
}
/**
* Shows a message about the server crashing due to an out of memory issue
*/
async function showOOMMessage(): Promise<void> {
const DOCS = 'More info...';
const DOUBLE = 'Double allocated memory';
const result = await window.showErrorMessage('The XML Language Server crashed due to an Out Of Memory Error, and will not be restarted. ', //
DOUBLE, DOCS);
if (result === DOCS) {
Telemetry.sendTelemetry(Telemetry.OPEN_OOM_DOCS_EVT);
await commands.executeCommand(ClientCommandConstants.OPEN_DOCS,
{
page: 'Troubleshooting',
section: 'the-language-server-crashes-due-to-an-out-of-memory-error'
}
);
} else if (result === DOUBLE) {
doubleAllocatedMemory();
}
}
const HEAP_DUMP_FOLDER_EXTRACTOR = new RegExp(`${HEAP_DUMP_LOCATION}(?:'([^']+)'|"([^"]+)"|([^\\s]+))`);
const MAX_HEAP_SIZE_EXTRACTOR = new RegExp(`-Xmx([0-9]+)[kKmMgG]`);
/**
* Returns the heap dump folder defined in the user's preferences, or undefined if the user does not set the heap dump folder
*
* @returns the heap dump folder defined in the user's preferences, or undefined if the user does not set the heap dump folder
*/
function getHeapDumpFolderFromSettings(): string {
const jvmArgs: string = workspace.getConfiguration('xml.server').get('vmargs');
const results = HEAP_DUMP_FOLDER_EXTRACTOR.exec(jvmArgs);
if (!results || !results[0]) {
return undefined;
}
return results[1] || results[2] || results[3];
}
const XMX_EXTRACTOR = /-Xmx([^\s]+)/;
/**
* Returns the value that the user set for Xmx, or DEFAULT if the user didn't set Xmx
*
* @returns the value that the user set for Xmx, or DEFAULT if the user didn't set Xmx
*/
function getXmxFromSettings(): string {
const vmargs: string = workspace.getConfiguration('xml.server').get('vmargs', null);
if (vmargs != null) {
const extractOfVmargs: RegExpExecArray = XMX_EXTRACTOR.exec(vmargs);
if (extractOfVmargs.length && extractOfVmargs[1]) {
return extractOfVmargs[1];
}
}
return 'DEFAULT';
}
/**
* Double the memory allocated to lemminx in the vmargs parameter
*/
async function doubleAllocatedMemory() {
let vmargs: string = workspace.getConfiguration('xml.server').get('vmargs', null);
const results = MAX_HEAP_SIZE_EXTRACTOR.exec(vmargs);
if (results && results[0]) {
const maxMemArg: string = results[0];
const maxMemValue = Number(results[1]);
const newMaxMemArg: string = maxMemArg.replace(maxMemValue.toString(), (maxMemValue * 2).toString());
vmargs = vmargs.replace(maxMemArg, newMaxMemArg);
await workspace.getConfiguration().update("xml.server.vmargs", vmargs, ConfigurationTarget.Global);
} else {
// by default, many JVM take 1/4 of the physical memory as -Xmx
// in the case it crashes, set -Xmx to half of total physical memory, in megabytes
vmargs = `-Xmx ${Math.trunc(totalmem()/2/1000000)}m ${vmargs}`;
await workspace.getConfiguration().update("xml.server.vmargs", vmargs, ConfigurationTarget.Global);
}
}