[debug] Shut down previous session properly before starting a new one

This commit is contained in:
Miro Spönemann 2020-01-21 15:26:33 +01:00
parent 68db44fa49
commit 2ba95947de
5 changed files with 87 additions and 17 deletions

View File

@ -0,0 +1,55 @@
import { DebugSessionManager } from "@theia/debug/lib/browser/debug-session-manager";
import { DebugSessionOptions } from "@theia/debug/lib/browser/debug-session-options";
import { DebugSession } from "@theia/debug/lib/browser/debug-session";
export class ArduinoDebugSessionManager extends DebugSessionManager {
static readonly COOL_DOWN_TIME = 5000;
protected arduinoSession?: Promise<DebugSession | undefined>;
protected lastSessionStopTime?: DOMHighResTimeStamp;
start(options: DebugSessionOptions) {
if (options.configuration.type === 'arduino') {
if (this.arduinoSession) {
this.messageService.info('A debug session is already running. You must stop the running session before starting a new one.')
return Promise.resolve(undefined);
}
const superStart = super.start.bind(this);
const promise = (async resolve => {
if (this.lastSessionStopTime) {
const now = performance.now();
if (now - this.lastSessionStopTime < ArduinoDebugSessionManager.COOL_DOWN_TIME) {
const waitTime = ArduinoDebugSessionManager.COOL_DOWN_TIME - Math.max(now - this.lastSessionStopTime, 0);
if (waitTime > 2000) {
const userWaitTime = Math.round(waitTime / 100) / 10;
this.messageService.info(`The previous debug session is cooling down. Waiting ${userWaitTime} seconds before starting a new session...`)
}
await new Promise(resolve => setTimeout(resolve, waitTime));
}
}
return superStart(options);
})();
this.arduinoSession = promise;
promise.then(session => {
if (!session)
this.arduinoSession = undefined;
});
return promise;
}
return super.start(options);
}
destroy(sessionId?: string): void {
if (this.arduinoSession) {
this.arduinoSession.then(session => {
if (session && sessionId === session.id) {
this.arduinoSession = undefined;
this.lastSessionStopTime = performance.now();
}
})
}
super.destroy(sessionId);
}
}

View File

@ -1,14 +1,17 @@
import { ContainerModule } from 'inversify'; import { ContainerModule } from 'inversify';
import { VariableContribution } from '@theia/variable-resolver/lib/browser'; import { VariableContribution } from '@theia/variable-resolver/lib/browser';
import { ArduinoVariableResolver } from './arduino-variable-resolver'; import { ArduinoVariableResolver } from './arduino-variable-resolver';
import { DebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager';
import { DebugFrontendApplicationContribution } from '@theia/debug/lib/browser/debug-frontend-application-contribution'; import { DebugFrontendApplicationContribution } from '@theia/debug/lib/browser/debug-frontend-application-contribution';
import { DebugConfigurationManager } from '@theia/debug/lib/browser/debug-configuration-manager'; import { DebugConfigurationManager } from '@theia/debug/lib/browser/debug-configuration-manager';
import { ArduinoDebugConfigurationManager } from './arduino-debug-configuration-manager'; import { ArduinoDebugConfigurationManager } from './arduino-debug-configuration-manager';
import { ArduinoDebugFrontendApplicationContribution } from './arduino-debug-frontend-application-contribution'; import { ArduinoDebugFrontendApplicationContribution } from './arduino-debug-frontend-application-contribution';
import { ArduinoDebugSessionManager } from './arduino-debug-session-manager';
export default new ContainerModule((bind, unbind, isBound, rebind) => { export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(ArduinoVariableResolver).toSelf().inSingletonScope(); bind(ArduinoVariableResolver).toSelf().inSingletonScope();
bind(VariableContribution).toService(ArduinoVariableResolver); bind(VariableContribution).toService(ArduinoVariableResolver);
rebind(DebugSessionManager).to(ArduinoDebugSessionManager).inSingletonScope();
rebind(DebugConfigurationManager).to(ArduinoDebugConfigurationManager).inSingletonScope(); rebind(DebugConfigurationManager).to(ArduinoDebugConfigurationManager).inSingletonScope();
rebind(DebugFrontendApplicationContribution).to(ArduinoDebugFrontendApplicationContribution); rebind(DebugFrontendApplicationContribution).to(ArduinoDebugFrontendApplicationContribution);
}); });

View File

@ -65,7 +65,7 @@ export class ArduinoDebugAdapterContribution implements DebugAdapterContribution
args = args.concat([path.join(__dirname, 'debug-adapter', 'main')]); args = args.concat([path.join(__dirname, 'debug-adapter', 'main')]);
return { return {
command: 'node', command: process.execPath,
args: args, args: args,
} }
} }

View File

@ -40,7 +40,11 @@ export abstract class AbstractServer extends EventEmitter {
protected launchReject?: (error: any) => void; protected launchReject?: (error: any) => void;
protected timer?: NodeJS.Timer; protected timer?: NodeJS.Timer;
public spawn(args: CmsisRequestArguments): Promise<void> { get isRunning(): boolean {
return !!this.process && !this.process.killed;
}
spawn(args: CmsisRequestArguments): Promise<void> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
this.launchResolve = resolve; this.launchResolve = resolve;
this.launchReject = reject; this.launchReject = reject;
@ -80,7 +84,7 @@ export abstract class AbstractServer extends EventEmitter {
}); });
} }
public kill() { kill() {
if (this.process) { if (this.process) {
this.process.kill('SIGINT'); this.process.kill('SIGINT');
} }

View File

@ -262,18 +262,14 @@ export class CmsisDebugSession extends GDBDebugSession {
this.sendEvent(new OutputEvent(`Starting debugger: ${JSON.stringify(args)}`)); this.sendEvent(new OutputEvent(`Starting debugger: ${JSON.stringify(args)}`));
await this.gdbServer.spawn(args); await this.gdbServer.spawn(args);
await this.spawn(args); await this.spawn(args);
if (gdbServerErrors.length > 0) { this.checkServerErrors(gdbServerErrors);
throw new Error(gdbServerErrors.join('\n'));
}
// Send commands // Send commands
await mi.sendTargetAsyncOn(this.gdb); await mi.sendTargetAsyncOn(this.gdb);
await mi.sendTargetSelectRemote(this.gdb, remote); await mi.sendTargetSelectRemote(this.gdb, remote);
await mi.sendMonitorResetHalt(this.gdb); await mi.sendMonitorResetHalt(this.gdb);
this.sendEvent(new OutputEvent(`Attached to debugger on port ${port}`)); this.sendEvent(new OutputEvent(`Attached to debugger on port ${port}`));
if (gdbServerErrors.length > 0) { this.checkServerErrors(gdbServerErrors);
throw new Error(gdbServerErrors.join('\n'));
}
// Download image // Download image
const progressListener = (percent: number) => this.progressEvent(percent, 'Loading Image'); const progressListener = (percent: number) => this.progressEvent(percent, 'Loading Image');
@ -291,9 +287,7 @@ export class CmsisDebugSession extends GDBDebugSession {
await mi.sendBreakOnFunction(this.gdb); await mi.sendBreakOnFunction(this.gdb);
} }
if (gdbServerErrors.length > 0) { this.checkServerErrors(gdbServerErrors);
throw new Error(gdbServerErrors.join('\n'));
}
this.sendEvent(new OutputEvent(`Image loaded: ${args.program}`)); this.sendEvent(new OutputEvent(`Image loaded: ${args.program}`));
this.sendEvent(new InitializedEvent()); this.sendEvent(new InitializedEvent());
@ -304,6 +298,12 @@ export class CmsisDebugSession extends GDBDebugSession {
}); });
} }
private checkServerErrors(errors: any[]): void {
if (errors.length > 0) {
throw new Error(errors.join('\n'));
}
}
protected spawn(args: CmsisRequestArguments): Promise<void> { protected spawn(args: CmsisRequestArguments): Promise<void> {
const varRegexp = /\$\{.*\}/; const varRegexp = /\$\{.*\}/;
if (args.gdb && varRegexp.test(args.gdb)) { if (args.gdb && varRegexp.test(args.gdb)) {
@ -427,11 +427,19 @@ export class CmsisDebugSession extends GDBDebugSession {
} }
// Stop gdb client and server - we give GDB five seconds to exit orderly before we kill the GDB server // Stop gdb client and server - we give GDB five seconds to exit orderly before we kill the GDB server
setTimeout(() => this.gdbServer.kill(), 5000); if (this.gdbServer.isRunning) {
try { const killPromise = new Promise(resolve => {
await this.gdb.sendGDBExit(); setTimeout(() => {
} catch (e) { this.gdbServer.kill();
// Need to catch here in case the connection has already been closed resolve();
}, 5000);
});
try {
await this.gdb.sendGDBExit();
} catch (e) {
// Need to catch here in case the connection has already been closed
}
await killPromise;
} }
} }