error-handling.js 6.9 KB

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