error-handling.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  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 Pool = require('../')
  8. describe('pool error handling', function () {
  9. it('Should complete these queries without dying', function (done) {
  10. const pool = new Pool()
  11. let errors = 0
  12. let shouldGet = 0
  13. function runErrorQuery() {
  14. shouldGet++
  15. return new Promise(function (resolve, reject) {
  16. pool
  17. .query("SELECT 'asd'+1 ")
  18. .then(function (res) {
  19. reject(res) // this should always error
  20. })
  21. .catch(function (err) {
  22. errors++
  23. resolve(err)
  24. })
  25. })
  26. }
  27. const ps = []
  28. for (let i = 0; i < 5; i++) {
  29. ps.push(runErrorQuery())
  30. }
  31. Promise.all(ps).then(function () {
  32. expect(shouldGet).to.eql(errors)
  33. pool.end(done)
  34. })
  35. })
  36. describe('calling release more than once', () => {
  37. it(
  38. 'should throw each time',
  39. co.wrap(function* () {
  40. const pool = new Pool()
  41. const client = yield pool.connect()
  42. client.release()
  43. expect(() => client.release()).to.throwError()
  44. expect(() => client.release()).to.throwError()
  45. return yield pool.end()
  46. })
  47. )
  48. it('should throw each time with callbacks', function (done) {
  49. const pool = new Pool()
  50. pool.connect(function (err, client, clientDone) {
  51. expect(err).not.to.be.an(Error)
  52. clientDone()
  53. expect(() => clientDone()).to.throwError()
  54. expect(() => clientDone()).to.throwError()
  55. pool.end(done)
  56. })
  57. })
  58. })
  59. describe('using an ended pool', () => {
  60. it('rejects all additional promises', (done) => {
  61. const pool = new Pool()
  62. const promises = []
  63. pool.end().then(() => {
  64. const squash = (promise) => promise.catch((e) => 'okay!')
  65. promises.push(squash(pool.connect()))
  66. promises.push(squash(pool.query('SELECT NOW()')))
  67. promises.push(squash(pool.end()))
  68. Promise.all(promises).then((res) => {
  69. expect(res).to.eql(['okay!', 'okay!', 'okay!'])
  70. done()
  71. })
  72. })
  73. })
  74. it('returns an error on all additional callbacks', (done) => {
  75. const pool = new Pool()
  76. pool.end(() => {
  77. pool.query('SELECT *', (err) => {
  78. expect(err).to.be.an(Error)
  79. pool.connect((err) => {
  80. expect(err).to.be.an(Error)
  81. pool.end((err) => {
  82. expect(err).to.be.an(Error)
  83. done()
  84. })
  85. })
  86. })
  87. })
  88. })
  89. })
  90. describe('error from idle client', () => {
  91. it(
  92. 'removes client from pool',
  93. co.wrap(function* () {
  94. const pool = new Pool()
  95. const client = yield pool.connect()
  96. expect(pool.totalCount).to.equal(1)
  97. expect(pool.waitingCount).to.equal(0)
  98. expect(pool.idleCount).to.equal(0)
  99. client.release()
  100. yield new Promise((resolve, reject) => {
  101. process.nextTick(() => {
  102. let poolError
  103. pool.once('error', (err) => {
  104. poolError = err
  105. })
  106. let clientError
  107. client.once('error', (err) => {
  108. clientError = err
  109. })
  110. client.emit('error', new Error('expected'))
  111. expect(clientError.message).to.equal('expected')
  112. expect(poolError.message).to.equal('expected')
  113. expect(pool.idleCount).to.equal(0)
  114. expect(pool.totalCount).to.equal(0)
  115. pool.end().then(resolve, reject)
  116. })
  117. })
  118. })
  119. )
  120. })
  121. describe('error from in-use client', () => {
  122. it(
  123. 'keeps the client in the pool',
  124. co.wrap(function* () {
  125. const pool = new Pool()
  126. const client = yield pool.connect()
  127. expect(pool.totalCount).to.equal(1)
  128. expect(pool.waitingCount).to.equal(0)
  129. expect(pool.idleCount).to.equal(0)
  130. yield new Promise((resolve, reject) => {
  131. process.nextTick(() => {
  132. let poolError
  133. pool.once('error', (err) => {
  134. poolError = err
  135. })
  136. let clientError
  137. client.once('error', (err) => {
  138. clientError = err
  139. })
  140. client.emit('error', new Error('expected'))
  141. expect(clientError.message).to.equal('expected')
  142. expect(poolError).not.to.be.ok()
  143. expect(pool.idleCount).to.equal(0)
  144. expect(pool.totalCount).to.equal(1)
  145. client.release()
  146. pool.end().then(resolve, reject)
  147. })
  148. })
  149. })
  150. )
  151. })
  152. describe('passing a function to pool.query', () => {
  153. it('calls back with error', (done) => {
  154. const pool = new Pool()
  155. console.log('passing fn to query')
  156. pool.query((err) => {
  157. expect(err).to.be.an(Error)
  158. pool.end(done)
  159. })
  160. })
  161. })
  162. describe('pool with lots of errors', () => {
  163. it(
  164. 'continues to work and provide new clients',
  165. co.wrap(function* () {
  166. const pool = new Pool({ max: 1 })
  167. const errors = []
  168. for (var i = 0; i < 20; i++) {
  169. try {
  170. yield pool.query('invalid sql')
  171. } catch (err) {
  172. errors.push(err)
  173. }
  174. }
  175. expect(errors).to.have.length(20)
  176. expect(pool.idleCount).to.equal(0)
  177. expect(pool.query).to.be.a(Function)
  178. const res = yield pool.query('SELECT $1::text as name', ['brianc'])
  179. expect(res.rows).to.have.length(1)
  180. expect(res.rows[0].name).to.equal('brianc')
  181. return pool.end()
  182. })
  183. )
  184. })
  185. it('should continue with queued items after a connection failure', (done) => {
  186. const closeServer = net
  187. .createServer((socket) => {
  188. socket.destroy()
  189. })
  190. .unref()
  191. closeServer.listen(() => {
  192. const pool = new Pool({ max: 1, port: closeServer.address().port, host: 'localhost' })
  193. pool.connect((err) => {
  194. expect(err).to.be.an(Error)
  195. if (err.code) {
  196. expect(err.code).to.be('ECONNRESET')
  197. }
  198. })
  199. pool.connect((err) => {
  200. expect(err).to.be.an(Error)
  201. if (err.code) {
  202. expect(err.code).to.be('ECONNRESET')
  203. }
  204. closeServer.close(() => {
  205. pool.end(done)
  206. })
  207. })
  208. })
  209. })
  210. it('handles post-checkout client failures in pool.query', (done) => {
  211. const pool = new Pool({ max: 1 })
  212. pool.on('error', () => {
  213. // We double close the connection in this test, prevent exception caused by that
  214. })
  215. pool.query('SELECT pg_sleep(5)', [], (err) => {
  216. expect(err).to.be.an(Error)
  217. done()
  218. })
  219. setTimeout(() => {
  220. pool._clients[0].end()
  221. }, 1000)
  222. })
  223. })