diff --git a/lib/client.js b/lib/client.js index 15c4de27d1..2817a30947 100644 --- a/lib/client.js +++ b/lib/client.js @@ -25,6 +25,7 @@ const { makeEscape } = require('./query/string'); const { uniqueId, cloneDeep, defaults } = require('lodash'); const Logger = require('./logger'); +const { KnexTimeoutError } = require('./util/timeout'); const debug = require('debug')('knex:client'); const _debugQuery = require('debug')('knex:query'); @@ -344,11 +345,15 @@ Object.assign(Client.prototype, { debug('acquired connection from pool: %s', connection.__knexUid); return connection; }) - .catch(TimeoutError, () => { - throw new Bluebird.TimeoutError( - 'Knex: Timeout acquiring a connection. The pool is probably full. ' + - 'Are you missing a .transacting(trx) call?' - ); + .catch((error) => { + let convertedError = error; + if (error instanceof TimeoutError) { + convertedError = new KnexTimeoutError( + 'Knex: Timeout acquiring a connection. The pool is probably full. ' + + 'Are you missing a .transacting(trx) call?' + ); + } + throw convertedError; }); } catch (e) { return Bluebird.reject(e); diff --git a/lib/dialects/mysql/index.js b/lib/dialects/mysql/index.js index f8aef43e20..0c18c05180 100644 --- a/lib/dialects/mysql/index.js +++ b/lib/dialects/mysql/index.js @@ -4,6 +4,7 @@ const inherits = require('inherits'); const { map, defer } = require('lodash'); const { promisify } = require('util'); const Client = require('../../client'); +const { timeout } = require('../../util/timeout'); const Bluebird = require('bluebird'); const Transaction = require('./transaction'); @@ -177,8 +178,7 @@ Object.assign(Client_MySQL.prototype, { // Purposely not putting timeout on `KILL QUERY` execution because erroring // early there would release the `connectionToKill` back to the pool with // a `KILL QUERY` command yet to finish. - return acquiringConn - .timeout(100) + return timeout(acquiringConn, 100) .then((conn) => this.query(conn, { method: 'raw', diff --git a/lib/dialects/oracledb/transaction.js b/lib/dialects/oracledb/transaction.js index c3491cc872..a759470032 100644 --- a/lib/dialects/oracledb/transaction.js +++ b/lib/dialects/oracledb/transaction.js @@ -2,6 +2,7 @@ const { isUndefined } = require('lodash'); const Bluebird = require('bluebird'); const Transaction = require('../../transaction'); +const { timeout, KnexTimeoutError } = require('../../util/timeout'); const debugTx = require('debug')('knex:tx'); module.exports = class Oracle_Transaction extends Transaction { @@ -23,24 +24,24 @@ module.exports = class Oracle_Transaction extends Transaction { } rollback(conn, err) { - const self = this; this._completed = true; debugTx('%s: rolling back', this.txid); - return conn - .rollbackAsync() - .timeout(5000) - .catch(Bluebird.TimeoutError, function(e) { - self._rejecter(e); + return timeout(conn.rollbackAsync(), 5000) + .catch((e) => { + if (!(e instanceof KnexTimeoutError)) { + return Promise.reject(e); + } + this._rejecter(e); }) - .then(function() { + .then(() => { if (isUndefined(err)) { - if (self.doNotRejectOnRollback) { - self._resolver(); + if (this.doNotRejectOnRollback) { + this._resolver(); return; } err = new Error(`Transaction rejected with non-error: ${err}`); } - self._rejecter(err); + this._rejecter(err); }); } diff --git a/lib/knex.js b/lib/knex.js index 92f8d8b060..7ae490dd9b 100644 --- a/lib/knex.js +++ b/lib/knex.js @@ -5,6 +5,7 @@ const QueryInterface = require('./query/methods'); const makeKnex = require('./util/make-knex'); const parseConnection = require('./util/parse-connection'); +const { KnexTimeoutError } = require('./util/timeout'); const fakeClient = require('./util/fake-client'); const { SUPPORTED_CLIENTS } = require('./constants'); const { resolveClientNameWithAliases } = require('./helpers'); @@ -58,6 +59,9 @@ function Knex(config) { // Expose Client on the main Knex namespace. Knex.Client = Client; + +Knex.KnexTimeoutError = KnexTimeoutError; + Knex.QueryBuilder = { extend: function(methodName, fn) { QueryBuilder.extend(methodName, fn); diff --git a/lib/runner.js b/lib/runner.js index 4c1f4f29a8..f3809e6855 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -1,4 +1,6 @@ const Bluebird = require('bluebird'); +const { KnexTimeoutError } = require('./util/timeout'); +const { timeout } = require('./util/timeout'); let PassThrough; @@ -133,7 +135,7 @@ Object.assign(Runner.prototype, { let queryPromise = this.client.query(this.connection, obj); if (obj.timeout) { - queryPromise = queryPromise.timeout(obj.timeout); + queryPromise = timeout(queryPromise, obj.timeout); } // Await the return value of client.processResponse; in the case of sqlite3's @@ -164,7 +166,10 @@ Object.assign(Runner.prototype, { return postProcessedResponse; }) - .catch(Bluebird.TimeoutError, (error) => { + .catch((error) => { + if (!(error instanceof KnexTimeoutError)) { + return Promise.reject(error); + } const { timeout, sql, bindings } = obj; let cancelQuery; @@ -241,7 +246,10 @@ Object.assign(Runner.prototype, { } return this.client .acquireConnection() - .catch(Bluebird.TimeoutError, (error) => { + .catch((error) => { + if (!(error instanceof KnexTimeoutError)) { + return Promise.reject(error); + } if (this.builder) { error.sql = this.builder.sql; error.bindings = this.builder.bindings; diff --git a/lib/transaction.js b/lib/transaction.js index e704a00e2f..c2c4ed1f37 100644 --- a/lib/transaction.js +++ b/lib/transaction.js @@ -5,6 +5,7 @@ const { EventEmitter } = require('events'); const Debug = require('debug'); const makeKnex = require('./util/make-knex'); +const { timeout, KnexTimeoutError } = require('./util/timeout'); const debug = Debug('knex:tx'); @@ -150,19 +151,26 @@ class Transaction extends EventEmitter { } rollback(conn, error) { - return this.query(conn, 'ROLLBACK', 2, error) - .timeout(5000) - .catch(Bluebird.TimeoutError, () => { + return timeout(this.query(conn, 'ROLLBACK', 2, error), 5000).catch( + (err) => { + if (!(err instanceof KnexTimeoutError)) { + return Promise.reject(err); + } this._rejecter(error); - }); + } + ); } rollbackTo(conn, error) { - return this.query(conn, `ROLLBACK TO SAVEPOINT ${this.txid}`, 2, error) - .timeout(5000) - .catch(Bluebird.TimeoutError, () => { - this._rejecter(error); - }); + return timeout( + this.query(conn, `ROLLBACK TO SAVEPOINT ${this.txid}`, 2, error), + 5000 + ).catch((err) => { + if (!(err instanceof KnexTimeoutError)) { + return Promise.reject(err); + } + this._rejecter(error); + }); } query(conn, sql, status, value) { diff --git a/lib/util/timeout.js b/lib/util/timeout.js new file mode 100644 index 0000000000..f05cd5694a --- /dev/null +++ b/lib/util/timeout.js @@ -0,0 +1,20 @@ +const Bluebird = require('bluebird'); +const delay = require('./delay'); + +class KnexTimeoutError extends Error { + constructor(message) { + super(message); + this.name = 'KnexTimeoutError'; + } +} + +module.exports.KnexTimeoutError = KnexTimeoutError; +module.exports.timeout = (promise, ms) => + Bluebird.resolve( + Promise.race([ + promise, + delay(ms).then(() => + Promise.reject(new KnexTimeoutError('operation timed out')) + ), + ]) + ); diff --git a/test/integration/builder/additional.js b/test/integration/builder/additional.js index 49864d347c..ce9d68d758 100644 --- a/test/integration/builder/additional.js +++ b/test/integration/builder/additional.js @@ -739,7 +739,7 @@ module.exports = function(knex) { .catch(function(error) { expect(_.pick(error, 'timeout', 'name', 'message')).to.deep.equal({ timeout: 200, - name: 'TimeoutError', + name: 'KnexTimeoutError', message: 'Defined query timeout of 200ms exceeded when running query.', }); @@ -826,7 +826,7 @@ module.exports = function(knex) { .catch(function(error) { expect(_.pick(error, 'timeout', 'name', 'message')).to.deep.equal({ timeout: 200, - name: 'TimeoutError', + name: 'KnexTimeoutError', message: 'Defined query timeout of 200ms exceeded when running query.', }); @@ -911,7 +911,7 @@ module.exports = function(knex) { .catch(function(error) { expect(_.pick(error, 'timeout', 'name', 'message')).to.deep.equal({ timeout: 1, - name: 'TimeoutError', + name: 'KnexTimeoutError', message: 'After query timeout of 1ms exceeded, cancelling of query failed.', }); @@ -980,7 +980,7 @@ module.exports = function(knex) { .catch(function(error) { expect(_.pick(error, 'timeout', 'name', 'message')).to.deep.equal({ timeout: secondQueryTimeout, - name: 'TimeoutError', + name: 'KnexTimeoutError', message: `Defined query timeout of ${secondQueryTimeout}ms exceeded when running query.`, }); }) diff --git a/test/integration/builder/transaction.js b/test/integration/builder/transaction.js index 71d2766ce2..e82ad9d760 100644 --- a/test/integration/builder/transaction.js +++ b/test/integration/builder/transaction.js @@ -6,6 +6,7 @@ const Bluebird = require('bluebird'); const Knex = require('../../../knex'); const _ = require('lodash'); const sinon = require('sinon'); +const { KnexTimeoutError } = require('../../../lib/util/timeout'); module.exports = function(knex) { // Certain dialects do not have proper insert with returning, so if this is true @@ -505,7 +506,7 @@ module.exports = function(knex) { .then(function() { throw new Error('should not get here'); }) - .catch(Bluebird.TimeoutError, function(error) {}); + .catch(KnexTimeoutError, function(error) {}); }); /** diff --git a/test/tape/invalid-db-setup.js b/test/tape/invalid-db-setup.js index 89bf10da2c..e15826390d 100644 --- a/test/tape/invalid-db-setup.js +++ b/test/tape/invalid-db-setup.js @@ -3,7 +3,7 @@ const tape = require('tape'); const _ = require('lodash'); const makeKnex = require('../../knex'); -const Bluebird = require('bluebird'); +const { KnexTimeoutError } = require('../../lib/util/timeout'); module.exports = (knexfile) => { Object.keys(knexfile).forEach((key) => { @@ -29,7 +29,7 @@ module.exports = (knexfile) => { .then((res) => { t.fail(`Query should have failed, got: ${JSON.stringify(res)}`); }) - .catch(Bluebird.TimeoutError, (e) => { + .catch(KnexTimeoutError, (e) => { t.fail(`Query should have failed with non timeout error`); }) .catch((e) => { @@ -54,7 +54,7 @@ module.exports = (knexfile) => { `Stream query should have failed, got: ${JSON.stringify(res)}` ); }) - .catch(Bluebird.TimeoutError, (e) => { + .catch(KnexTimeoutError, (e) => { t.fail(`Stream query should have failed with non timeout error`); }) .catch((e) => { @@ -104,7 +104,7 @@ module.exports = (knexfile) => { .then(() => { t.fail('query should have stalled'); }) - .catch(Bluebird.TimeoutError, (e) => { + .catch(KnexTimeoutError, (e) => { t.pass('Got acquireTimeout error'); }) .catch((e) => { diff --git a/types/index.d.ts b/types/index.d.ts index eeec0a949f..b2a6c54565 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1990,6 +1990,8 @@ declare namespace Knex { ) => QueryBuilder ): void; } + + export class KnexTimeoutError extends Error {} } export = Knex;