Browse Source

fix(lnd): send SIGINT to lnd to terminate

renovate/lint-staged-8.x
Tom Kirkpatrick 6 years ago
parent
commit
3326db1e55
No known key found for this signature in database GPG Key ID: 72203A8EC5967EA8
  1. 27
      app/lib/lnd/neutrino.js
  2. 55
      app/lib/zap/controller.js
  3. 15
      test/unit/lnd/neutrino.spec.js

27
app/lib/lnd/neutrino.js

@ -15,7 +15,7 @@ const CHAIN_SYNC_COMPLETE = 'chain-sync-finished'
// Events // Events
const ERROR = 'error' const ERROR = 'error'
const CLOSE = 'close' const EXIT = 'exit'
const WALLET_UNLOCKER_GRPC_ACTIVE = 'wallet-unlocker-grpc-active' const WALLET_UNLOCKER_GRPC_ACTIVE = 'wallet-unlocker-grpc-active'
const LIGHTNING_GRPC_ACTIVE = 'lightning-grpc-active' const LIGHTNING_GRPC_ACTIVE = 'lightning-grpc-active'
const GOT_CURRENT_BLOCK_HEIGHT = 'got-current-block-height' const GOT_CURRENT_BLOCK_HEIGHT = 'got-current-block-height'
@ -90,11 +90,25 @@ class Neutrino extends EventEmitter {
// neutrinoArgs.push('--neutrino.connect=testnet2-btcd.zaphq.io') // neutrinoArgs.push('--neutrino.connect=testnet2-btcd.zaphq.io')
} }
mainLog.info(
'Spawning Neutrino process: %s %s',
this.lndConfig.binaryPath,
neutrinoArgs.join(' ')
)
this.process = spawn(this.lndConfig.binaryPath, neutrinoArgs) this.process = spawn(this.lndConfig.binaryPath, neutrinoArgs)
.on('error', error => this.emit(ERROR, error)) .on('error', error => {
.on('close', code => { mainLog.debug('Neutrino process received "error" event with error: %s', error)
this.emit(CLOSE, code, this.lastError) this.emit(ERROR, error, this.lastError)
})
.on('exit', (code, signal) => {
mainLog.debug(
'Neutrino process received "exit" event with code %s and signal %s',
code,
signal
)
this.process = null this.process = null
this.emit(EXIT, code, signal, this.lastError)
}) })
// Listen for when neutrino prints data to stderr. // Listen for when neutrino prints data to stderr.
@ -230,11 +244,10 @@ class Neutrino extends EventEmitter {
/** /**
* Stop the Lnd process. * Stop the Lnd process.
*/ */
kill() { kill(signalName: string = 'SIGINT') {
if (this.process) { if (this.process) {
mainLog.info('Killing Neutrino process...') mainLog.info('Killing Neutrino process...')
this.process.kill() this.process.kill(signalName)
this.process = null
} }
} }

55
app/lib/zap/controller.js

@ -288,11 +288,8 @@ class ZapController {
} }
} }
/**
/** /**
* Starts the LND node and attach event listeners. * Starts the LND node and attach event listeners.
* @param {string} alias Alias to assign to the lnd node.
* @param {boolean} autopilot True if autopilot should be enabled.
* @return {Neutrino} Neutrino instance. * @return {Neutrino} Neutrino instance.
*/ */
startNeutrino() { startNeutrino() {
@ -307,12 +304,12 @@ class ZapController {
}) })
}) })
this.neutrino.on('close', (code, lastError) => { this.neutrino.on('exit', (code, signal, lastError) => {
mainLog.info(`Lnd process has shut down (code ${code})`) mainLog.info(`Lnd process has shut down (code: ${code}, signal: ${signal})`)
if (this.is('running') || this.is('connected')) { if (this.is('running') || this.is('connected')) {
dialog.showMessageBox({ dialog.showMessageBox({
type: 'error', type: 'error',
message: `Lnd has unexpectedly quit: ${lastError}` message: `Lnd has unexpectedly quit:\n\nError code: ${code}\nExit signal: ${signal}\nLast error: ${lastError}`
}) })
this.terminate() this.terminate()
} }
@ -358,61 +355,45 @@ class ZapController {
*/ */
async shutdownNeutrino() { async shutdownNeutrino() {
// We only want to shut down LND if we are running it locally. // We only want to shut down LND if we are running it locally.
if (this.lndConfig.type !== 'local') { if (this.lndConfig.type !== 'local' || !this.neutrino || !this.neutrino.process) {
return Promise.resolve() return Promise.resolve()
} }
// Attempt a graceful shutdown if we can.
if (this.lightning && this.lightning.can('terminate')) {
mainLog.info('Shutting down Neutrino...') mainLog.info('Shutting down Neutrino...')
return new Promise(resolve => { return new Promise(async resolve => {
// HACK: Sometimes there are errors during the shutdown process that prevent the daeming from shutting down at // HACK: Sometimes there are errors during the shutdown process that prevent the daeming from shutting down at
// all. If we haven't received notification of the process closing within 10 seconds, kill it. // all. If we haven't received notification of the process closing within 10 seconds, kill it.
// See https://github.com/lightningnetwork/lnd/pull/1781 // See https://github.com/lightningnetwork/lnd/pull/1781
// See https://github.com/lightningnetwork/lnd/pull/1783 // See https://github.com/lightningnetwork/lnd/pull/1783
const shutdownTimeout = setTimeout(() => { const shutdownTimeout = setTimeout(() => {
this.neutrino.removeListener('close', closeHandler) this.neutrino.removeListener('exit', exitHandler)
if (this.neutrino) { if (this.neutrino) {
mainLog.warn('Graceful shutdown failed to complete within 10 seconds.') mainLog.warn('Graceful shutdown failed to complete within 10 seconds.')
this.neutrino.kill() this.neutrino.kill('SIGTERM')
resolve() resolve()
} }
}, 1000 * 10) }, 1000 * 10)
// HACK: The Lightning.stopDaemon` call returns before lnd has actually fully completed the shutdown process const exitHandler = () => {
// so we add a listener on the close event so that we can wrap things up once the process has been fully closed
// out.
const closeHandler = function() {
mainLog.info('Neutrino shutdown complete.')
clearTimeout(shutdownTimeout) clearTimeout(shutdownTimeout)
resolve() resolve()
} }
this.neutrino.once('close', closeHandler) this.neutrino.once('exit', exitHandler)
this.lightning // The Lightning service is only active once the wallet has been unlocked and a gRPC connection has been made.
.terminate() // If it is active, disconnect from it before we terminate neutrino.
.then(() => mainLog.info('Neutrino Daemon shutdown complete')) if (this.lightning && this.lightning.can('terminate')) {
.catch(err => { await this.lightning.disconnect()
mainLog.error('Unable to gracefully shutdown LND: %o', err)
// Kill the process ourselves here to ensure that we don't leave hanging processes.
if (this.neutrino) {
this.neutrino.kill()
resolve()
}
})
})
} }
// Kill the Neutrino process (sends SIGINT to Neutrino process)
// The Lightning service is only active after the wallet has been unlocked and a gRPC connection has been
// established. In this case, kill the Neutrino process to ensure that we don't leave hanging process.
// FIXME: This currencly doesn't do a graceful shutdown as LND does not properly handle SIGTERM.
// See https://github.com/lightningnetwork/lnd/issues/1028
else if (this.neutrino) {
this.neutrino.kill() this.neutrino.kill()
} }).then(() => mainLog.info('Neutrino shutdown complete'))
} }
/**
* Start or connect to lnd process after onboarding has been completed by the app.
*/
finishOnboarding(options: onboardingOptions) { finishOnboarding(options: onboardingOptions) {
mainLog.info('Finishing onboarding') mainLog.info('Finishing onboarding')
// Save the lnd config options that we got from the renderer. // Save the lnd config options that we got from the renderer.

15
test/unit/lnd/neutrino.spec.js

@ -200,19 +200,4 @@ describe('Neutrino', function() {
}) })
}) })
}) })
describe('.kill', () => {
describe('called when neutrino is already running', () => {
beforeEach(() => {
this.neutrino = new Neutrino(new LndConfig())
this.neutrino.process = {
kill: jest.fn()
}
this.neutrino.kill()
})
it('should kill the neutrino process', () => {
expect(this.neutrino.process).toBeNull()
})
})
})
}) })

Loading…
Cancel
Save