connection-timeout.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. 'use strict'
  2. const net = require('net')
  3. const co = require('co')
  4. const expect = require('expect.js')
  5. const describe = require('mocha').describe
  6. const it = require('mocha').it
  7. const before = require('mocha').before
  8. const after = require('mocha').after
  9. const Pool = require('../')
  10. describe('connection timeout', () => {
  11. const connectionFailure = new Error('Temporary connection failure')
  12. before((done) => {
  13. this.server = net.createServer((socket) => {
  14. socket.on('data', () => {
  15. // discard any buffered data or the server wont terminate
  16. })
  17. })
  18. this.server.listen(() => {
  19. this.port = this.server.address().port
  20. done()
  21. })
  22. })
  23. after((done) => {
  24. this.server.close(done)
  25. })
  26. it('should callback with an error if timeout is passed', (done) => {
  27. const pool = new Pool({ connectionTimeoutMillis: 10, port: this.port, host: 'localhost' })
  28. pool.connect((err, client, release) => {
  29. expect(err).to.be.an(Error)
  30. expect(err.message).to.contain('timeout')
  31. expect(client).to.equal(undefined)
  32. expect(pool.idleCount).to.equal(0)
  33. done()
  34. })
  35. })
  36. it('should reject promise with an error if timeout is passed', (done) => {
  37. const pool = new Pool({ connectionTimeoutMillis: 10, port: this.port, host: 'localhost' })
  38. pool.connect().catch((err) => {
  39. expect(err).to.be.an(Error)
  40. expect(err.message).to.contain('timeout')
  41. expect(pool.idleCount).to.equal(0)
  42. done()
  43. })
  44. })
  45. it(
  46. 'should handle multiple timeouts',
  47. co.wrap(
  48. function* () {
  49. const errors = []
  50. const pool = new Pool({ connectionTimeoutMillis: 1, port: this.port, host: 'localhost' })
  51. for (var i = 0; i < 15; i++) {
  52. try {
  53. yield pool.connect()
  54. } catch (e) {
  55. errors.push(e)
  56. }
  57. }
  58. expect(errors).to.have.length(15)
  59. }.bind(this)
  60. )
  61. )
  62. it('should timeout on checkout of used connection', (done) => {
  63. const pool = new Pool({ connectionTimeoutMillis: 100, max: 1 })
  64. pool.connect((err, client, release) => {
  65. expect(err).to.be(undefined)
  66. expect(client).to.not.be(undefined)
  67. pool.connect((err, client) => {
  68. expect(err).to.be.an(Error)
  69. expect(client).to.be(undefined)
  70. release()
  71. pool.end(done)
  72. })
  73. })
  74. })
  75. it('should not break further pending checkouts on a timeout', (done) => {
  76. const pool = new Pool({ connectionTimeoutMillis: 200, max: 1 })
  77. pool.connect((err, client, releaseOuter) => {
  78. expect(err).to.be(undefined)
  79. pool.connect((err, client) => {
  80. expect(err).to.be.an(Error)
  81. expect(client).to.be(undefined)
  82. releaseOuter()
  83. })
  84. setTimeout(() => {
  85. pool.connect((err, client, releaseInner) => {
  86. expect(err).to.be(undefined)
  87. expect(client).to.not.be(undefined)
  88. releaseInner()
  89. pool.end(done)
  90. })
  91. }, 100)
  92. })
  93. })
  94. it('should timeout on query if all clients are busy', (done) => {
  95. const pool = new Pool({ connectionTimeoutMillis: 100, max: 1 })
  96. pool.connect((err, client, release) => {
  97. expect(err).to.be(undefined)
  98. expect(client).to.not.be(undefined)
  99. pool.query('select now()', (err, result) => {
  100. expect(err).to.be.an(Error)
  101. expect(result).to.be(undefined)
  102. release()
  103. pool.end(done)
  104. })
  105. })
  106. })
  107. it('should recover from timeout errors', (done) => {
  108. const pool = new Pool({ connectionTimeoutMillis: 100, max: 1 })
  109. pool.connect((err, client, release) => {
  110. expect(err).to.be(undefined)
  111. expect(client).to.not.be(undefined)
  112. pool.query('select now()', (err, result) => {
  113. expect(err).to.be.an(Error)
  114. expect(result).to.be(undefined)
  115. release()
  116. pool.query('select $1::text as name', ['brianc'], (err, res) => {
  117. expect(err).to.be(undefined)
  118. expect(res.rows).to.have.length(1)
  119. pool.end(done)
  120. })
  121. })
  122. })
  123. })
  124. it('continues processing after a connection failure', (done) => {
  125. const Client = require('pg').Client
  126. const orgConnect = Client.prototype.connect
  127. let called = false
  128. Client.prototype.connect = function (cb) {
  129. // Simulate a failure on first call
  130. if (!called) {
  131. called = true
  132. return setTimeout(() => {
  133. cb(connectionFailure)
  134. }, 100)
  135. }
  136. // And pass-through the second call
  137. orgConnect.call(this, cb)
  138. }
  139. const pool = new Pool({
  140. Client: Client,
  141. connectionTimeoutMillis: 1000,
  142. max: 1,
  143. })
  144. pool.connect((err, client, release) => {
  145. expect(err).to.be(connectionFailure)
  146. pool.query('select $1::text as name', ['brianc'], (err, res) => {
  147. expect(err).to.be(undefined)
  148. expect(res.rows).to.have.length(1)
  149. pool.end(done)
  150. })
  151. })
  152. })
  153. it('releases newly connected clients if the queued already timed out', (done) => {
  154. const Client = require('pg').Client
  155. const orgConnect = Client.prototype.connect
  156. let connection = 0
  157. Client.prototype.connect = function (cb) {
  158. // Simulate a failure on first call
  159. if (connection === 0) {
  160. connection++
  161. return setTimeout(() => {
  162. cb(connectionFailure)
  163. }, 300)
  164. }
  165. // And second connect taking > connection timeout
  166. if (connection === 1) {
  167. connection++
  168. return setTimeout(() => {
  169. orgConnect.call(this, cb)
  170. }, 1000)
  171. }
  172. orgConnect.call(this, cb)
  173. }
  174. const pool = new Pool({
  175. Client: Client,
  176. connectionTimeoutMillis: 1000,
  177. max: 1,
  178. })
  179. // Direct connect
  180. pool.connect((err, client, release) => {
  181. expect(err).to.be(connectionFailure)
  182. })
  183. // Queued
  184. let called = 0
  185. pool.connect((err, client, release) => {
  186. // Verify the callback is only called once
  187. expect(called++).to.be(0)
  188. expect(err).to.be.an(Error)
  189. pool.query('select $1::text as name', ['brianc'], (err, res) => {
  190. expect(err).to.be(undefined)
  191. expect(res.rows).to.have.length(1)
  192. pool.end(done)
  193. })
  194. })
  195. })
  196. })