diff --git a/arduino-debugger-extension/src/browser/arduino-debug-session-manager.ts b/arduino-debugger-extension/src/browser/arduino-debug-session-manager.ts new file mode 100644 index 00000000..027ee692 --- /dev/null +++ b/arduino-debugger-extension/src/browser/arduino-debug-session-manager.ts @@ -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; + 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); + } + +} diff --git a/arduino-debugger-extension/src/browser/frontend-module.ts b/arduino-debugger-extension/src/browser/frontend-module.ts index f3223dba..d02ff16e 100644 --- a/arduino-debugger-extension/src/browser/frontend-module.ts +++ b/arduino-debugger-extension/src/browser/frontend-module.ts @@ -1,14 +1,17 @@ import { ContainerModule } from 'inversify'; import { VariableContribution } from '@theia/variable-resolver/lib/browser'; 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 { DebugConfigurationManager } from '@theia/debug/lib/browser/debug-configuration-manager'; import { ArduinoDebugConfigurationManager } from './arduino-debug-configuration-manager'; import { ArduinoDebugFrontendApplicationContribution } from './arduino-debug-frontend-application-contribution'; +import { ArduinoDebugSessionManager } from './arduino-debug-session-manager'; export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(ArduinoVariableResolver).toSelf().inSingletonScope(); bind(VariableContribution).toService(ArduinoVariableResolver); + rebind(DebugSessionManager).to(ArduinoDebugSessionManager).inSingletonScope(); rebind(DebugConfigurationManager).to(ArduinoDebugConfigurationManager).inSingletonScope(); rebind(DebugFrontendApplicationContribution).to(ArduinoDebugFrontendApplicationContribution); }); diff --git a/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts b/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts index 5b1f62ad..910a506f 100644 --- a/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts +++ b/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts @@ -65,7 +65,7 @@ export class ArduinoDebugAdapterContribution implements DebugAdapterContribution args = args.concat([path.join(__dirname, 'debug-adapter', 'main')]); return { - command: 'node', + command: process.execPath, args: args, } } diff --git a/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts b/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts index 0a839038..09a98331 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts @@ -40,7 +40,11 @@ export abstract class AbstractServer extends EventEmitter { protected launchReject?: (error: any) => void; protected timer?: NodeJS.Timer; - public spawn(args: CmsisRequestArguments): Promise { + get isRunning(): boolean { + return !!this.process && !this.process.killed; + } + + spawn(args: CmsisRequestArguments): Promise { return new Promise(async (resolve, reject) => { this.launchResolve = resolve; this.launchReject = reject; @@ -80,7 +84,7 @@ export abstract class AbstractServer extends EventEmitter { }); } - public kill() { + kill() { if (this.process) { this.process.kill('SIGINT'); } diff --git a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts index 5dffc940..095b7ecc 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts @@ -262,18 +262,14 @@ export class CmsisDebugSession extends GDBDebugSession { this.sendEvent(new OutputEvent(`Starting debugger: ${JSON.stringify(args)}`)); await this.gdbServer.spawn(args); await this.spawn(args); - if (gdbServerErrors.length > 0) { - throw new Error(gdbServerErrors.join('\n')); - } + this.checkServerErrors(gdbServerErrors); // Send commands await mi.sendTargetAsyncOn(this.gdb); await mi.sendTargetSelectRemote(this.gdb, remote); await mi.sendMonitorResetHalt(this.gdb); this.sendEvent(new OutputEvent(`Attached to debugger on port ${port}`)); - if (gdbServerErrors.length > 0) { - throw new Error(gdbServerErrors.join('\n')); - } + this.checkServerErrors(gdbServerErrors); // Download image const progressListener = (percent: number) => this.progressEvent(percent, 'Loading Image'); @@ -291,9 +287,7 @@ export class CmsisDebugSession extends GDBDebugSession { await mi.sendBreakOnFunction(this.gdb); } - if (gdbServerErrors.length > 0) { - throw new Error(gdbServerErrors.join('\n')); - } + this.checkServerErrors(gdbServerErrors); this.sendEvent(new OutputEvent(`Image loaded: ${args.program}`)); 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 { const varRegexp = /\$\{.*\}/; 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 - setTimeout(() => this.gdbServer.kill(), 5000); - try { - await this.gdb.sendGDBExit(); - } catch (e) { - // Need to catch here in case the connection has already been closed + if (this.gdbServer.isRunning) { + const killPromise = new Promise(resolve => { + setTimeout(() => { + this.gdbServer.kill(); + resolve(); + }, 5000); + }); + try { + await this.gdb.sendGDBExit(); + } catch (e) { + // Need to catch here in case the connection has already been closed + } + await killPromise; } }