From eea94a88963603a36290f38de598c8342fd3b1e8 Mon Sep 17 00:00:00 2001
From: wubzz
Date: Mon, 1 Feb 2016 20:40:06 +0100
Subject: [PATCH 1/4] TimeoutError when acquiring con is not possible
---
index.html | 21 ++++++++++++++
src/runner.js | 20 ++++++++++++-
test/integration/builder/transaction.js | 37 +++++++++++++++++++++++++
3 files changed, 77 insertions(+), 1 deletion(-)
diff --git a/index.html b/index.html
index 8ff966657f..5bb5b94928 100644
--- a/index.html
+++ b/index.html
@@ -60,6 +60,7 @@
– client
– debug
– pooling
+ – acquireConnectionTimeout
– migrations
@@ -504,6 +505,26 @@ Pooling
You may use knex.destroy by passing a callback, or by chaining as a promise, just not both.
+
+ acquireConnectionTimeout
+
+
+ acquireConnectionTimeout defaults to 60000ms and is used to determine how long knex should wait before throwing a timeout error when acquiring a connection is not possible.
+ The most common cause for this is using up all the pool for transaction connections and then attempting to run queries outside of transactions while the pool is still full.
+ The error thrown will provide information on the query the connection was for to simplify the job of locating the culprit.
+
+
+
+var knex = require('knex')({
+ client: 'pg',
+ connection: {...},
+ pool: {...},
+ acquireConnectionTimeout: 10000
+ });
+
+
+
+
Migrations
diff --git a/src/runner.js b/src/runner.js
index 4143eea82e..29072f3e25 100644
--- a/src/runner.js
+++ b/src/runner.js
@@ -135,8 +135,26 @@ assign(Runner.prototype, {
// Check whether there's a transaction flag, and that it has a connection.
ensureConnection: function() {
var runner = this
+ var acquireConnectionTimeout = runner.client.config.acquireConnectionTimeout || 60000
return Promise.try(function() {
- return runner.connection || runner.client.acquireConnection()
+ return runner.connection || new Promise((resolver, rejecter) => {
+ runner.client.acquireConnection()
+ .timeout(acquireConnectionTimeout)
+ .then(resolver)
+ .catch(Promise.TimeoutError, (error) => {
+ var timeoutError = new Error('Knex: Timeout acquiring a connection. The pool is probably full. Are you missing a .transacting(trx) call?')
+ var additionalErrorInformation = {
+ timeoutStack: error.stack
+ }
+ if(runner.builder) {
+ additionalErrorInformation.sql = runner.builder.sql
+ additionalErrorInformation.bindings = runner.builder.bindings
+ }
+ assign(timeoutError, additionalErrorInformation)
+ rejecter(timeoutError)
+ })
+ .catch(rejecter)
+ })
}).disposer(function() {
if (runner.connection.__knex__disposed) return
runner.client.releaseConnection(runner.connection)
diff --git a/test/integration/builder/transaction.js b/test/integration/builder/transaction.js
index d2619ff93e..0e33d6de43 100644
--- a/test/integration/builder/transaction.js
+++ b/test/integration/builder/transaction.js
@@ -3,6 +3,8 @@
'use strict';
var Promise = testPromise;
+var Knex = require('../../../knex');
+var _ = require('lodash');
module.exports = function(knex) {
@@ -277,6 +279,41 @@ module.exports = function(knex) {
});
+ it('#1040, #1171 - When pool is filled with transaction connections, Non-transaction queries should not hang the application, but instead throw a timeout error', function(done) {
+ this.timeout(15000);
+ //To make this test easier, I'm changing the pool settings to max 1.
+ var knexConfig = _.clone(knex.client.config);
+ knexConfig.pool.min = 0;
+ knexConfig.pool.max = 1;
+ knexConfig.acquireConnectionTimeout = 10000;
+
+ var knexDb = new Knex(knexConfig);
+
+ //Create a transaction that will occupy the only available connection, and avoid trx.commit.
+ knexDb.transaction(function(trx) {
+ trx.raw('SELECT version()').then(function() {
+
+ //No connection is available, so try issuing a query without transaction.
+ //Since there is no available connection, it should throw a timeout error based on knex pool config.
+ knexDb.raw('select * FROM accounts WHERE username = ?', ['Test'])
+ .then(function(res) {
+ expect(res).to.not.exist();
+ })
+ .catch(function(error) {
+ expect(error.bindings).to.be.an('array');
+ expect(error.bindings[0]).to.equal('Test');
+ expect(error.sql).to.equal('select * FROM accounts WHERE username = ?');
+ expect(error.message).to.equal('Knex: Timeout acquiring a connection. The pool is probably full. Are you missing a .transacting(trx) call?');
+ trx.commit();
+ });
+ }).catch(trx.rollback);
+ })
+ .then(function() {
+ done();
+ })
+ .catch(done);
+ });
+
});
};
From 80ee5a8021920020b2d87988cd451f67d618aea0 Mon Sep 17 00:00:00 2001
From: wubzz
Date: Mon, 1 Feb 2016 20:54:14 +0100
Subject: [PATCH 2/4] select version() => select 1 = 1 for test
version() doesn't exist in all dbs.
---
test/integration/builder/transaction.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/integration/builder/transaction.js b/test/integration/builder/transaction.js
index 0e33d6de43..60d2753ac4 100644
--- a/test/integration/builder/transaction.js
+++ b/test/integration/builder/transaction.js
@@ -291,7 +291,7 @@ module.exports = function(knex) {
//Create a transaction that will occupy the only available connection, and avoid trx.commit.
knexDb.transaction(function(trx) {
- trx.raw('SELECT version()').then(function() {
+ trx.raw('SELECT 1 = 1').then(function() {
//No connection is available, so try issuing a query without transaction.
//Since there is no available connection, it should throw a timeout error based on knex pool config.
From 57bac6cd583b7a4f1fff450a369ec975b4c6d26a Mon Sep 17 00:00:00 2001
From: wubzz
Date: Mon, 1 Feb 2016 21:27:30 +0100
Subject: [PATCH 3/4] Fix some indentation and test comment
---
src/runner.js | 3 +++
test/integration/builder/transaction.js | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/runner.js b/src/runner.js
index 29072f3e25..7c8d64c447 100644
--- a/src/runner.js
+++ b/src/runner.js
@@ -146,11 +146,14 @@ assign(Runner.prototype, {
var additionalErrorInformation = {
timeoutStack: error.stack
}
+
if(runner.builder) {
additionalErrorInformation.sql = runner.builder.sql
additionalErrorInformation.bindings = runner.builder.bindings
}
+
assign(timeoutError, additionalErrorInformation)
+
rejecter(timeoutError)
})
.catch(rejecter)
diff --git a/test/integration/builder/transaction.js b/test/integration/builder/transaction.js
index 60d2753ac4..83318eee42 100644
--- a/test/integration/builder/transaction.js
+++ b/test/integration/builder/transaction.js
@@ -294,7 +294,7 @@ module.exports = function(knex) {
trx.raw('SELECT 1 = 1').then(function() {
//No connection is available, so try issuing a query without transaction.
- //Since there is no available connection, it should throw a timeout error based on knex pool config.
+ //Since there is no available connection, it should throw a timeout error based on `aquireConnectionTimeout` from the knex config.
knexDb.raw('select * FROM accounts WHERE username = ?', ['Test'])
.then(function(res) {
expect(res).to.not.exist();
From 4244bb1ec266783e4a9d5f1acabfbd24d0c2b491 Mon Sep 17 00:00:00 2001
From: wubzz
Date: Wed, 3 Feb 2016 22:50:45 +0100
Subject: [PATCH 4/4] Simplify the test and lower the time to 1sec
---
test/integration/builder/transaction.js | 44 +++++++++++--------------
1 file changed, 19 insertions(+), 25 deletions(-)
diff --git a/test/integration/builder/transaction.js b/test/integration/builder/transaction.js
index 83318eee42..cc0a23cead 100644
--- a/test/integration/builder/transaction.js
+++ b/test/integration/builder/transaction.js
@@ -279,39 +279,33 @@ module.exports = function(knex) {
});
- it('#1040, #1171 - When pool is filled with transaction connections, Non-transaction queries should not hang the application, but instead throw a timeout error', function(done) {
- this.timeout(15000);
+ it('#1040, #1171 - When pool is filled with transaction connections, Non-transaction queries should not hang the application, but instead throw a timeout error', function() {
//To make this test easier, I'm changing the pool settings to max 1.
var knexConfig = _.clone(knex.client.config);
knexConfig.pool.min = 0;
knexConfig.pool.max = 1;
- knexConfig.acquireConnectionTimeout = 10000;
+ knexConfig.acquireConnectionTimeout = 1000;
var knexDb = new Knex(knexConfig);
//Create a transaction that will occupy the only available connection, and avoid trx.commit.
- knexDb.transaction(function(trx) {
- trx.raw('SELECT 1 = 1').then(function() {
-
- //No connection is available, so try issuing a query without transaction.
- //Since there is no available connection, it should throw a timeout error based on `aquireConnectionTimeout` from the knex config.
- knexDb.raw('select * FROM accounts WHERE username = ?', ['Test'])
- .then(function(res) {
- expect(res).to.not.exist();
- })
- .catch(function(error) {
- expect(error.bindings).to.be.an('array');
- expect(error.bindings[0]).to.equal('Test');
- expect(error.sql).to.equal('select * FROM accounts WHERE username = ?');
- expect(error.message).to.equal('Knex: Timeout acquiring a connection. The pool is probably full. Are you missing a .transacting(trx) call?');
- trx.commit();
- });
- }).catch(trx.rollback);
- })
- .then(function() {
- done();
- })
- .catch(done);
+ return knexDb.transaction(function(trx) {
+ trx.raw('SELECT 1 = 1').then(function () {
+ //No connection is available, so try issuing a query without transaction.
+ //Since there is no available connection, it should throw a timeout error based on `aquireConnectionTimeout` from the knex config.
+ return knexDb.raw('select * FROM accounts WHERE username = ?', ['Test'])
+ })
+ .then(function () {
+ //Should never reach this point
+ expect(false).to.be.ok();
+ }).catch(function (error) {
+ expect(error.bindings).to.be.an('array');
+ expect(error.bindings[0]).to.equal('Test');
+ expect(error.sql).to.equal('select * FROM accounts WHERE username = ?');
+ expect(error.message).to.equal('Knex: Timeout acquiring a connection. The pool is probably full. Are you missing a .transacting(trx) call?');
+ trx.commit();//Test done
+ });
+ });
});
});