Skip to content
This repository has been archived by the owner on Dec 11, 2019. It is now read-only.

Fix some more error branches, assert sanity, and automatically test it all. #14703

Merged
merged 6 commits into from
Jul 10, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 44 additions & 20 deletions app/tor.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,13 @@ class TorDaemon extends EventEmitter {

/**
* Kill the tor daemon.
*
* Idempotent. Repeated calls have no effect.
*/
kill () {
if (this._watcher === null) {
return
}
assert(this._watcher)
this._watcher.close()
this._watcher = null
Expand All @@ -165,6 +170,17 @@ class TorDaemon extends EventEmitter {
}
}

/**
* Internal subroutine. Report an error and kill the daemon.
*
* @param {string} msg - error message
*/
_error (msg) {
console.log(msg)
this.emit('error', msg)
this.kill()
}

/**
* Internal subroutine. Called by fs.watch when the tor watch
* directory is changed. If the control port file is newly written,
Expand Down Expand Up @@ -490,9 +506,10 @@ class TorDaemon extends EventEmitter {

const readable = controlSocket
const writable = controlSocket
this._control = new TorControl(readable, writable)
this._control.on('error', (err) => this._controlError(err))
this._control.on('close', () => this._controlClosed())
const control = new TorControl(readable, writable)
this._control = control
this._control.on('error', (err) => this._controlError(control, err))
this._control.on('close', () => this._controlClosed(control))

// We have finished polling, _and_ we are scheduled to be
// notified either by (a) our file system activity watcher, or
Expand All @@ -508,22 +525,16 @@ class TorDaemon extends EventEmitter {
}
}
if (err) {
console.log(`tor: authentication failure: ${err}`)
this.kill()
return
return this._error(`tor: authentication failure: ${err}`)
}
this._control.getSOCKSListeners((err, listeners) => {
if (err) {
console.log(`tor: failed to get socks addresses: ${err}`)
this.kill()
return
return this._error(`tor: failed to get socks addresses: ${err}`)
}
this._socks_addresses = listeners
this._control.getVersion((err, version) => {
if (err) {
console.log(`tor: failed to get version: ${err}`)
this.kill()
return
return this._error(`tor: failed to get version: ${err}`)
}
this._tor_version = version
this.emit('launch', this.getSOCKSAddress())
Expand All @@ -538,12 +549,17 @@ class TorDaemon extends EventEmitter {
* socket. Report it to the console and give up by destroying the
* control connection.
*
* @param {TorControl} control
* @param {Error} err
*/
_controlError (err) {
assert(this._control)
_controlError (control, err) {
if (this._control === control) {
this._control = null
} else {
console.log('tor: control error got deferred')
}
console.log(`tor: control socket error: ${err}`)
this._control.destroy(err)
control.destroy(err)
}

/*
Expand All @@ -552,10 +568,15 @@ class TorDaemon extends EventEmitter {
* has exited.
*
* TODO(riastradh): Also try to restart tor or anything?
*
* @param {TorControl} control
*/
_controlClosed () {
assert(this._control)
this._control = null
_controlClosed (control) {
if (this._control === control) {
this._control = null
} else {
console.log('tor: control closure got deferred')
}
// Assume this means the process exited.
this.emit('exit')
// Poll in case we received a watch event for file system activity
Expand Down Expand Up @@ -944,10 +965,14 @@ class TorControl extends EventEmitter {
* - Mark the control closed so it can't be used any more.
* - Pass an error to all callbacks waiting for command completion.
*
* Idempotent. Repeated calls have no effect.
*
* @param {Error} err
*/
destroy (err) {
assert(!this._destroyed)
if (this._destroyed) {
return
}
this._readable.destroy(err)
this._writable.end()
this._writable.destroy(err)
Expand All @@ -962,7 +987,6 @@ class TorControl extends EventEmitter {
const [, callback] = this._cmdq.shift()
callback(err, null, null)
}
setImmediate(() => assert(this._closing === 0))
}

/**
Expand Down
37 changes: 37 additions & 0 deletions test/integration/app/torTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -313,5 +313,42 @@ describe('tor unit tests', function () {
})
})
})

it('handles failure gracefully', function (callback) {
torDaemon.setup(() => {
// Spawn a process.
torProcess = spawnTor(torDaemon)
// Wait half a second to give the tor process a head start.
setTimeout(() => {
// Clobber the authentication cookie.
const zero = Buffer.alloc(32, 0)
fs.writeFile(tor.torControlCookiePath(), zero, (err) => {
if (err) {
return callback(err)
}
// Start watching.
torDaemon.start()
// Wait 2sec for error.
const errorTimeout = setTimeout(() => {
assert.fail('timeout on tor error')
}, 2000)
torDaemon.once('error', (err) => {
assert(err.toString().indexOf('Tor error 515:') !== -1)
clearTimeout(errorTimeout)
assert(err)
// Kill the tor process gently and wait for it to exit.
torProcess.kill('SIGTERM')
const timeoutExited = setTimeout(() => {
assert.fail('tor process failed to exit after 2sec')
}, 2000)
torProcess.once('exit', () => {
clearTimeout(timeoutExited)
callback()
})
})
})
}, 500)
})
})
})
})