Skip to content

Commit 0d630e8

Browse files
committed
Improve server start implementation and provide better error reporting
- Report error for spawn() failures - Register callback for server exit, error and stderr to catch such errors - If error happens report the error with detailed message from stderr - Do healthCheck in 1 second intervals until it is successful or the overall timeout of 10s happens - Remove unnecessary warning messages when stopping server after startup failures. - Report error if trace server exits unexpectedly Signed-off-by: Bernd Hufmann <[email protected]>
1 parent a1da771 commit 0d630e8

File tree

1 file changed

+72
-24
lines changed

1 file changed

+72
-24
lines changed

src/trace-server.ts

Lines changed: 72 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const key = 'pid';
1313
const millis = 10000;
1414
const none = -1;
1515
const prefix = 'Trace Server';
16-
const suffix = ' failure or so.';
16+
const suffix = ' failure.';
1717
const SHUTDOWN_DELAY = 2000;
1818

1919
export class TraceServer {
@@ -24,15 +24,21 @@ export class TraceServer {
2424
const server = spawn(this.getPath(from), this.getArgs(from));
2525

2626
if (!server.pid) {
27-
this.showError(prefix + ' startup' + suffix);
27+
// Failed to spawn the child process due to errors, PID is undefined and an error is emitted.
28+
await new Promise<void>(resolve => {
29+
server.once('error', error => {
30+
this.showError(prefix + ' startup' + suffix + ': ' + error);
31+
});
32+
resolve();
33+
});
2834
return;
2935
}
3036
this.server = server;
3137
await context?.workspaceState.update(key, this.server.pid);
3238
await this.waitFor(context);
3339
}
3440

35-
async stopOrReset(context: vscode.ExtensionContext | undefined) {
41+
async stopOrReset(context: vscode.ExtensionContext | undefined, reportNotStopped: boolean = true) {
3642
const pid: number | undefined = context?.workspaceState.get(key);
3743
const not = prefix + ' not stopped as none running or owned by us.';
3844
if (pid === none) {
@@ -43,6 +49,7 @@ export class TraceServer {
4349
let id: NodeJS.Timeout;
4450
// recovering from workspaceState => no this.server set
4551
if (this.server) {
52+
this.server.removeAllListeners();
4653
this.server.once('exit', () => {
4754
this.showStatus(false);
4855
clearTimeout(id);
@@ -56,6 +63,7 @@ export class TraceServer {
5663
},
5764
async progress => {
5865
progress.report({ message: 'stopping...' });
66+
// Remove all existing listeners to avoid unnecessary pop-ups
5967
const message = prefix + ' stopping' + suffix + ' Resetting.';
6068
treeKill(pid, error => {
6169
if (error) {
@@ -66,7 +74,7 @@ export class TraceServer {
6674
});
6775
}
6876
);
69-
} else {
77+
} else if (reportNotStopped) {
7078
vscode.window.showWarningMessage(not);
7179
}
7280
await context?.workspaceState.update(key, none);
@@ -81,6 +89,9 @@ export class TraceServer {
8189
return;
8290
}
8391
if (pid) {
92+
// Remove all existing listeners to avoid unnecessary pop-ups
93+
this.server?.removeAllListeners();
94+
8495
treeKill(pid);
8596
// Allow the treeKill to finish collecting and killing all
8697
// spawned processes.
@@ -160,6 +171,10 @@ export class TraceServer {
160171
}
161172

162173
private async waitFor(context: vscode.ExtensionContext | undefined) {
174+
if (this.server === undefined) {
175+
return;
176+
}
177+
const server = this.server;
163178
await vscode.window.withProgress(
164179
{
165180
location: vscode.ProgressLocation.Notification,
@@ -168,27 +183,63 @@ export class TraceServer {
168183
},
169184
async progress => {
170185
progress.report({ message: 'starting up...' });
171-
let timeout = false;
172-
const timeoutId = setTimeout(() => (timeout = true), millis);
173-
174-
// eslint-disable-next-line no-constant-condition
175-
while (true) {
176-
if (await this.isUp()) {
177-
this.showStatus(true);
178-
clearTimeout(timeoutId);
179-
break;
180-
}
181-
if (timeout) {
182-
this.showError(prefix + ' startup timed-out after ' + millis + 'ms.');
183-
await this.stopOrReset(context);
184-
break;
186+
try {
187+
let timeout = false;
188+
const timeoutId = setTimeout(() => (timeout = true), millis);
189+
190+
let stderr = '';
191+
server.stderr?.once('data', data => {
192+
stderr = data;
193+
});
194+
195+
let errorString: string | undefined;
196+
server.once('exit', code => {
197+
errorString = 'Code: ' + code + (stderr !== '.' ? '. Error message: ' + stderr : '');
198+
});
199+
200+
// eslint-disable-next-line no-constant-condition
201+
while (true) {
202+
if (await this.isUp()) {
203+
clearTimeout(timeoutId);
204+
this.showStatus(true);
205+
break;
206+
}
207+
208+
// Check if trace server exited with error
209+
if (errorString !== undefined) {
210+
throw new Error(errorString);
211+
}
212+
213+
// Check for overall timeout
214+
if (timeout) {
215+
await this.stopOrReset(context, false);
216+
throw new Error(prefix + ' startup timed-out after ' + millis + 'ms.');
217+
}
218+
219+
// Wait before trying again
220+
await new Promise<void>(resolve => setTimeout(() => resolve(), 1000));
185221
}
222+
} catch (error) {
223+
this.showErrorDetailed(prefix + ' starting up' + suffix, error as Error);
224+
return;
225+
} finally {
226+
// Remove all listener needed during startup
227+
server.removeAllListeners();
228+
// Add listener to catch when server exits unexpectedly
229+
server.once('exit', code => {
230+
context?.workspaceState.update(key, none);
231+
this.server = undefined;
232+
this.setStatusIfAvailable(false);
233+
vscode.window.showErrorMessage(
234+
prefix + ' exited unexpectedly' + (code !== null ? ' with code ' + code : '') + '!'
235+
);
236+
});
186237
}
187238
}
188239
);
189240
}
190241

191-
private async isUp() {
242+
private async isUp(): Promise<boolean> {
192243
const client = new TspClient(this.getServerUrl());
193244
const health = await client.checkHealth();
194245
const status = health.getModel()?.status;
@@ -198,18 +249,15 @@ export class TraceServer {
198249
private async showError(message: string) {
199250
console.error(message);
200251
vscode.window.showErrorMessage(message);
201-
const disclaimer = ' running, despite this error.';
202252
const up = await this.isUp();
203253
if (up) {
204-
vscode.window.showWarningMessage(prefix + ' is still' + disclaimer);
205-
} else {
206-
vscode.window.showWarningMessage(prefix + ' is not' + disclaimer);
254+
vscode.window.showWarningMessage(prefix + ' is still running, despite this error.');
207255
}
208256
this.setStatusIfAvailable(up);
209257
}
210258

211259
private showErrorDetailed(message: string, error: Error) {
212-
const details = error.name + ' - ' + error.message;
260+
const details = error.name + ' - ' + error?.message;
213261
vscode.window.showErrorMessage(details);
214262
console.error(details);
215263
this.showError(message);

0 commit comments

Comments
 (0)