diff --git a/atom/app/node_main.cc b/atom/app/node_main.cc index ab55b49e0751d..c4dde00f9f7f8 100644 --- a/atom/app/node_main.cc +++ b/atom/app/node_main.cc @@ -96,6 +96,7 @@ int NodeMain(int argc, char* argv[]) { } } while (more == true); + node_debugger.Stop(); exit_code = node::EmitExit(env); node::RunAtExit(env); gin_env.platform()->DrainTasks(env->isolate()); diff --git a/atom/browser/atom_browser_main_parts.cc b/atom/browser/atom_browser_main_parts.cc index 41f41c85abf98..41230d0e179fd 100644 --- a/atom/browser/atom_browser_main_parts.cc +++ b/atom/browser/atom_browser_main_parts.cc @@ -481,6 +481,7 @@ void AtomBrowserMainParts::PostMainMessageLoopRun() { ui::SetX11ErrorHandlers(X11EmptyErrorHandler, X11EmptyIOErrorHandler); #endif + node_debugger_->Stop(); js_env_->OnMessageLoopDestroying(); #if defined(OS_MACOSX) diff --git a/atom/browser/node_debugger.cc b/atom/browser/node_debugger.cc index 2358f526b2dc6..83520fdc835e8 100644 --- a/atom/browser/node_debugger.cc +++ b/atom/browser/node_debugger.cc @@ -59,4 +59,10 @@ void NodeDebugger::Start() { DCHECK(env_->inspector_agent()->IsListening()); } +void NodeDebugger::Stop() { + auto* inspector = env_->inspector_agent(); + if (inspector && inspector->IsListening()) + inspector->Stop(); +} + } // namespace atom diff --git a/atom/browser/node_debugger.h b/atom/browser/node_debugger.h index be69f9cfd18e1..eb173d6908a0d 100644 --- a/atom/browser/node_debugger.h +++ b/atom/browser/node_debugger.h @@ -20,6 +20,7 @@ class NodeDebugger { ~NodeDebugger(); void Start(); + void Stop(); private: node::Environment* env_; diff --git a/spec/fixtures/module/delay-exit.js b/spec/fixtures/module/delay-exit.js new file mode 100644 index 0000000000000..fa7895449b32f --- /dev/null +++ b/spec/fixtures/module/delay-exit.js @@ -0,0 +1,3 @@ +const { app } = require('electron') + +process.on('message', () => app.quit()) diff --git a/spec/node-spec.js b/spec/node-spec.js index 7addaaac443d9..90a1d13259fa8 100644 --- a/spec/node-spec.js +++ b/spec/node-spec.js @@ -8,6 +8,8 @@ const os = require('os') const { ipcRenderer, remote } = require('electron') const features = process.atomBinding('features') +const { emittedOnce } = require('./events-helpers') + const isCI = remote.getGlobal('isCi') chai.use(dirtyChai) @@ -219,6 +221,7 @@ describe('node feature', () => { describe('inspector', () => { let child = null + let exitPromise = null beforeEach(function () { if (!features.isRunAsNodeEnabled()) { @@ -226,8 +229,14 @@ describe('node feature', () => { } }) - afterEach(() => { - if (child !== null) child.kill() + afterEach(async () => { + if (child && exitPromise) { + const [code, signal] = await exitPromise + expect(signal).to.equal(null) + expect(code).to.equal(0) + } else if (child) { + child.kill() + } }) it('supports starting the v8 inspector with --inspect/--inspect-brk', (done) => { @@ -263,6 +272,7 @@ describe('node feature', () => { ELECTRON_RUN_AS_NODE: true } }) + exitPromise = emittedOnce(child, 'exit') let output = '' function cleanup () { @@ -287,6 +297,7 @@ describe('node feature', () => { it('does not start the v8 inspector when --inspect is after a -- argument', (done) => { child = ChildProcess.spawn(remote.process.execPath, [path.join(__dirname, 'fixtures', 'module', 'noop.js'), '--', '--inspect']) + exitPromise = emittedOnce(child, 'exit') let output = '' function dataListener (data) { @@ -303,6 +314,35 @@ describe('node feature', () => { }) }) + it('does does not crash when quitting with the inspector connected', function (done) { + // IPC Electron child process not supported on Windows + if (process.platform === 'win32') return this.skip() + child = ChildProcess.spawn(remote.process.execPath, [path.join(__dirname, 'fixtures', 'module', 'delay-exit'), '--inspect=0'], { + stdio: ['ipc'] + }) + exitPromise = emittedOnce(child, 'exit') + + let output = '' + function dataListener (data) { + output += data + + if (output.trim().startsWith('Debugger listening on ws://') && output.endsWith('\n')) { + const socketMatch = output.trim().match(/(ws:\/\/.+:[0-9]+\/.+?)\n/gm) + if (socketMatch && socketMatch[0]) { + child.stderr.removeListener('data', dataListener) + child.stdout.removeListener('data', dataListener) + const connection = new WebSocket(socketMatch[0]) + connection.onopen = () => { + child.send('plz-quit') + done() + } + } + } + } + child.stderr.on('data', dataListener) + child.stdout.on('data', dataListener) + }) + it('supports js binding', (done) => { child = ChildProcess.spawn(process.execPath, ['--inspect', path.join(__dirname, 'fixtures', 'module', 'inspector-binding.js')], { env: { @@ -310,6 +350,7 @@ describe('node feature', () => { }, stdio: ['ipc'] }) + exitPromise = emittedOnce(child, 'exit') child.on('message', ({ cmd, debuggerEnabled, success }) => { if (cmd === 'assert') {