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. 85
      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
const ERROR = 'error'
const CLOSE = 'close'
const EXIT = 'exit'
const WALLET_UNLOCKER_GRPC_ACTIVE = 'wallet-unlocker-grpc-active'
const LIGHTNING_GRPC_ACTIVE = 'lightning-grpc-active'
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')
}
mainLog.info(
'Spawning Neutrino process: %s %s',
this.lndConfig.binaryPath,
neutrinoArgs.join(' ')
)
this.process = spawn(this.lndConfig.binaryPath, neutrinoArgs)
.on('error', error => this.emit(ERROR, error))
.on('close', code => {
this.emit(CLOSE, code, this.lastError)
.on('error', error => {
mainLog.debug('Neutrino process received "error" event with error: %s', error)
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.emit(EXIT, code, signal, this.lastError)
})
// Listen for when neutrino prints data to stderr.
@ -230,11 +244,10 @@ class Neutrino extends EventEmitter {
/**
* Stop the Lnd process.
*/
kill() {
kill(signalName: string = 'SIGINT') {
if (this.process) {
mainLog.info('Killing Neutrino process...')
this.process.kill()
this.process = null
this.process.kill(signalName)
}
}

85
app/lib/zap/controller.js

@ -288,11 +288,8 @@ class ZapController {
}
}
/**
/**
* 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.
*/
startNeutrino() {
@ -307,12 +304,12 @@ class ZapController {
})
})
this.neutrino.on('close', (code, lastError) => {
mainLog.info(`Lnd process has shut down (code ${code})`)
this.neutrino.on('exit', (code, signal, lastError) => {
mainLog.info(`Lnd process has shut down (code: ${code}, signal: ${signal})`)
if (this.is('running') || this.is('connected')) {
dialog.showMessageBox({
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()
}
@ -358,61 +355,45 @@ class ZapController {
*/
async shutdownNeutrino() {
// 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()
}
// Attempt a graceful shutdown if we can.
if (this.lightning && this.lightning.can('terminate')) {
mainLog.info('Shutting down Neutrino...')
return new Promise(resolve => {
// 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.
// See https://github.com/lightningnetwork/lnd/pull/1781
// See https://github.com/lightningnetwork/lnd/pull/1783
const shutdownTimeout = setTimeout(() => {
this.neutrino.removeListener('close', closeHandler)
if (this.neutrino) {
mainLog.warn('Graceful shutdown failed to complete within 10 seconds.')
this.neutrino.kill()
resolve()
}
}, 1000 * 10)
// HACK: The Lightning.stopDaemon` call returns before lnd has actually fully completed the shutdown process
// 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)
mainLog.info('Shutting down Neutrino...')
return new Promise(async resolve => {
// 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.
// See https://github.com/lightningnetwork/lnd/pull/1781
// See https://github.com/lightningnetwork/lnd/pull/1783
const shutdownTimeout = setTimeout(() => {
this.neutrino.removeListener('exit', exitHandler)
if (this.neutrino) {
mainLog.warn('Graceful shutdown failed to complete within 10 seconds.')
this.neutrino.kill('SIGTERM')
resolve()
}
this.neutrino.once('close', closeHandler)
this.lightning
.terminate()
.then(() => mainLog.info('Neutrino Daemon shutdown complete'))
.catch(err => {
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()
}
})
})
}
}, 1000 * 10)
const exitHandler = () => {
clearTimeout(shutdownTimeout)
resolve()
}
this.neutrino.once('exit', exitHandler)
// 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) {
// The Lightning service is only active once the wallet has been unlocked and a gRPC connection has been made.
// If it is active, disconnect from it before we terminate neutrino.
if (this.lightning && this.lightning.can('terminate')) {
await this.lightning.disconnect()
}
// Kill the Neutrino process (sends SIGINT to Neutrino process)
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) {
mainLog.info('Finishing onboarding')
// 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