diff --git a/src/Lookup.js b/src/Lookup.js index 4d5b0cc..7d0e753 100644 --- a/src/Lookup.js +++ b/src/Lookup.js @@ -45,6 +45,7 @@ class Lookup { * @param {Object} options * @param {number} options.family * @param {boolean} options.all + * @param {number} options.timeout * @param {Function} callback * @throws {Error} * @returns {{}|undefined} @@ -104,13 +105,14 @@ class Lookup { * @param {Object} options * @param {number} options.family * @param {boolean} options.all + * @param {number} options.timeout * @returns {Promise} * @private */ _resolve(hostname, options) { this._amountOfResolveTries[hostname] = this._amountOfResolveTries[hostname] || 0; - return this._innerResolve(hostname, options.family) + return this._innerResolve(hostname, options.family, options.timeout) .then(records => { // Corner case branch. // @@ -172,10 +174,11 @@ class Lookup { /** * @param {string} hostname * @param {number} ipVersion + * @param {number} timeout * @returns {Promise} * @private */ - _innerResolve(hostname, ipVersion) { + _innerResolve(hostname, ipVersion, timeout) { const key = `${hostname}_${ipVersion}`; return new Promise((resolve, reject) => { @@ -188,7 +191,7 @@ class Lookup { let task = this._tasksManager.find(key); if (!task) { - task = new ResolveTask(hostname, ipVersion); + task = new ResolveTask(hostname, ipVersion, timeout); this._tasksManager.add(key, task); diff --git a/src/ResolveTask.js b/src/ResolveTask.js index 326ce80..966a5d3 100644 --- a/src/ResolveTask.js +++ b/src/ResolveTask.js @@ -32,8 +32,9 @@ class ResolveTask extends EventEmitter { /** * @param {string} hostname * @param {number} ipVersion + * @param {number} timeout */ - constructor(hostname, ipVersion) { + constructor(hostname, ipVersion, timeout = 0) { super(); assert( @@ -48,6 +49,8 @@ class ResolveTask extends EventEmitter { this._hostname = hostname; this._ipVersion = ipVersion; + this._timeout = timeout; + this._timeoutHandle = undefined; this._resolver = ipVersion === ResolveTask.IPv4 ? dns.resolve4 : dns.resolve6; this._resolved = this._resolved.bind(this); @@ -56,6 +59,11 @@ class ResolveTask extends EventEmitter { } run() { + if (this._timeout) { + const error = new Error('Timed out while resolving DNS'); + error.code = 'ETIMEDOUT'; + this._timeoutHandle = setTimeout(this._resolved, this._timeout, error); + } this._resolver(this._hostname, {ttl: true}, this._resolved); } @@ -67,6 +75,10 @@ class ResolveTask extends EventEmitter { * @private */ _resolved(error, addresses) { + if (this._timeoutHandle !== undefined) { + clearTimeout(this._timeoutHandle); + this._timeoutHandle = undefined; + } if (error) { return this.emit('error', error); } diff --git a/tests/Unit/ResolveTask/run.js b/tests/Unit/ResolveTask/run.js index 5f69d43..996ac63 100644 --- a/tests/Unit/ResolveTask/run.js +++ b/tests/Unit/ResolveTask/run.js @@ -22,4 +22,31 @@ describe('Unit: ResolveTask::run', () => { assert.isTrue(resolverSpy.calledOnce); assert.isTrue(resolverSpy.calledWithExactly(hostname, {ttl: true}, task._resolved)); }); + + it('must run resolver with timeout', () => { + const timeout = 100; + const clock = sinon.useFakeTimers(); + try { + const resolverSpy = sinon.spy(); + + const task = new ResolveTask(hostname, ipVersion, timeout); + + let error = undefined; + task.on('error', err => error = err); + + task._resolver = resolverSpy; + + task.run(); + + assert.isDefined(task._timeoutHandle); + + clock.tick(timeout); + assert.isUndefined(task._timeoutHandle); + assert.isDefined(error); + assert(error.code === 'ETIMEDOUT'); + } finally { + clock.restore(); + } + + }); });