Skip to content

Commit 88fc848

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 fixes #52 Signed-off-by: Bernd Hufmann <[email protected]>
1 parent a1da771 commit 88fc848

File tree

1 file changed

+85
-24
lines changed

1 file changed

+85
-24
lines changed

src/trace-server.ts

Lines changed: 85 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,54 @@ 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 {
2020
private server: ChildProcess | undefined;
21+
private errorString: string | undefined;
22+
private stderr: string | undefined;
23+
private context: vscode.ExtensionContext | undefined;
24+
25+
private handleStartUpError = (code: number | null): void => {
26+
this.errorString = 'Code: ' + code + (this.stderr !== undefined ? '. Error message: ' + this.stderr : '.');
27+
};
28+
29+
private handleStartUpStderr = (data: string | undefined): void => {
30+
this.stderr = data;
31+
};
32+
33+
private handleError = (code: number | null): void => {
34+
if (this.context) {
35+
this.context?.workspaceState.update(key, none);
36+
}
37+
this.server = undefined;
38+
this.setStatusIfAvailable(false);
39+
vscode.window.showErrorMessage(
40+
prefix + ' exited unexpectedly' + (code !== null ? ' with code ' + code : '') + '!'
41+
);
42+
};
2143

2244
private async start(context: vscode.ExtensionContext | undefined) {
2345
const from = this.getSettings();
2446
const server = spawn(this.getPath(from), this.getArgs(from));
2547

2648
if (!server.pid) {
27-
this.showError(prefix + ' startup' + suffix);
49+
// Failed to spawn the child process due to errors, PID is undefined and an error is emitted.
50+
await new Promise<void>(resolve => {
51+
server.once('error', error => {
52+
this.showError(prefix + ' startup' + suffix + ': ' + error);
53+
});
54+
resolve();
55+
});
2856
return;
2957
}
3058
this.server = server;
3159
await context?.workspaceState.update(key, this.server.pid);
3260
await this.waitFor(context);
3361
}
3462

35-
async stopOrReset(context: vscode.ExtensionContext | undefined) {
63+
async stopOrReset(context: vscode.ExtensionContext | undefined, reportNotStopped: boolean = true) {
3664
const pid: number | undefined = context?.workspaceState.get(key);
3765
const not = prefix + ' not stopped as none running or owned by us.';
3866
if (pid === none) {
@@ -43,6 +71,8 @@ export class TraceServer {
4371
let id: NodeJS.Timeout;
4472
// recovering from workspaceState => no this.server set
4573
if (this.server) {
74+
this.server.off('exit', this.handleError);
75+
this.context = undefined;
4676
this.server.once('exit', () => {
4777
this.showStatus(false);
4878
clearTimeout(id);
@@ -66,7 +96,7 @@ export class TraceServer {
6696
});
6797
}
6898
);
69-
} else {
99+
} else if (reportNotStopped) {
70100
vscode.window.showWarningMessage(not);
71101
}
72102
await context?.workspaceState.update(key, none);
@@ -81,6 +111,9 @@ export class TraceServer {
81111
return;
82112
}
83113
if (pid) {
114+
// Remove all existing listeners to avoid unnecessary pop-ups
115+
this.server?.off('exit', this.handleError);
116+
this.context = undefined;
84117
treeKill(pid);
85118
// Allow the treeKill to finish collecting and killing all
86119
// spawned processes.
@@ -160,6 +193,12 @@ export class TraceServer {
160193
}
161194

162195
private async waitFor(context: vscode.ExtensionContext | undefined) {
196+
if (this.server === undefined) {
197+
return;
198+
}
199+
200+
this.context = context;
201+
const server = this.server;
163202
await vscode.window.withProgress(
164203
{
165204
location: vscode.ProgressLocation.Notification,
@@ -168,27 +207,52 @@ export class TraceServer {
168207
},
169208
async progress => {
170209
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;
210+
try {
211+
let timeout = false;
212+
const timeoutId = setTimeout(() => (timeout = true), millis);
213+
214+
server.stderr?.once('data', this.handleStartUpStderr);
215+
server.once('exit', this.handleStartUpError);
216+
217+
// eslint-disable-next-line no-constant-condition
218+
while (true) {
219+
if (await this.isUp()) {
220+
clearTimeout(timeoutId);
221+
this.showStatus(true);
222+
break;
223+
}
224+
225+
// Check if trace server exited with error
226+
if (this.errorString !== undefined) {
227+
const errorString = this.errorString;
228+
this.errorString = undefined;
229+
throw new Error(errorString);
230+
}
231+
232+
// Check for overall timeout
233+
if (timeout) {
234+
await this.stopOrReset(context, false);
235+
throw new Error(prefix + ' startup timed-out after ' + millis + 'ms.');
236+
}
237+
238+
// Wait before trying again
239+
await new Promise<void>(resolve => setTimeout(() => resolve(), 1000));
185240
}
241+
} catch (error) {
242+
this.showErrorDetailed(prefix + ' starting up' + suffix, error as Error);
243+
return;
244+
} finally {
245+
// Remove all listener needed during startup
246+
server.off('exit', this.handleStartUpError);
247+
server.stderr?.off('data', this.handleStartUpStderr);
248+
// Add listener to catch when server exits unexpectedly
249+
server.once('exit', this.handleError);
186250
}
187251
}
188252
);
189253
}
190254

191-
private async isUp() {
255+
private async isUp(): Promise<boolean> {
192256
const client = new TspClient(this.getServerUrl());
193257
const health = await client.checkHealth();
194258
const status = health.getModel()?.status;
@@ -198,18 +262,15 @@ export class TraceServer {
198262
private async showError(message: string) {
199263
console.error(message);
200264
vscode.window.showErrorMessage(message);
201-
const disclaimer = ' running, despite this error.';
202265
const up = await this.isUp();
203266
if (up) {
204-
vscode.window.showWarningMessage(prefix + ' is still' + disclaimer);
205-
} else {
206-
vscode.window.showWarningMessage(prefix + ' is not' + disclaimer);
267+
vscode.window.showWarningMessage(prefix + ' is still running, despite this error.');
207268
}
208269
this.setStatusIfAvailable(up);
209270
}
210271

211272
private showErrorDetailed(message: string, error: Error) {
212-
const details = error.name + ' - ' + error.message;
273+
const details = error.name + ' - ' + error?.message;
213274
vscode.window.showErrorMessage(details);
214275
console.error(details);
215276
this.showError(message);

0 commit comments

Comments
 (0)