connection-timeout.js 6.07 KB
Newer Older
bovornsiriampairat committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
'use strict'
const net = require('net')
const co = require('co')
const expect = require('expect.js')

const describe = require('mocha').describe
const it = require('mocha').it
const before = require('mocha').before
const after = require('mocha').after

const Pool = require('../')

describe('connection timeout', () => {
  const connectionFailure = new Error('Temporary connection failure')

  before((done) => {
    this.server = net.createServer((socket) => {
      socket.on('data', () => {
        // discard any buffered data or the server wont terminate
      })
    })

    this.server.listen(() => {
      this.port = this.server.address().port
      done()
    })
  })

  after((done) => {
    this.server.close(done)
  })

  it('should callback with an error if timeout is passed', (done) => {
    const pool = new Pool({ connectionTimeoutMillis: 10, port: this.port, host: 'localhost' })
    pool.connect((err, client, release) => {
      expect(err).to.be.an(Error)
      expect(err.message).to.contain('timeout')
      expect(client).to.equal(undefined)
      expect(pool.idleCount).to.equal(0)
      done()
    })
  })

  it('should reject promise with an error if timeout is passed', (done) => {
    const pool = new Pool({ connectionTimeoutMillis: 10, port: this.port, host: 'localhost' })
    pool.connect().catch((err) => {
      expect(err).to.be.an(Error)
      expect(err.message).to.contain('timeout')
      expect(pool.idleCount).to.equal(0)
      done()
    })
  })

  it(
    'should handle multiple timeouts',
    co.wrap(
      function* () {
        const errors = []
        const pool = new Pool({ connectionTimeoutMillis: 1, port: this.port, host: 'localhost' })
        for (var i = 0; i < 15; i++) {
          try {
            yield pool.connect()
          } catch (e) {
            errors.push(e)
          }
        }
        expect(errors).to.have.length(15)
      }.bind(this)
    )
  )

  it('should timeout on checkout of used connection', (done) => {
    const pool = new Pool({ connectionTimeoutMillis: 100, max: 1 })
    pool.connect((err, client, release) => {
      expect(err).to.be(undefined)
      expect(client).to.not.be(undefined)
      pool.connect((err, client) => {
        expect(err).to.be.an(Error)
        expect(client).to.be(undefined)
        release()
        pool.end(done)
      })
    })
  })

  it('should not break further pending checkouts on a timeout', (done) => {
    const pool = new Pool({ connectionTimeoutMillis: 200, max: 1 })
    pool.connect((err, client, releaseOuter) => {
      expect(err).to.be(undefined)

      pool.connect((err, client) => {
        expect(err).to.be.an(Error)
        expect(client).to.be(undefined)
        releaseOuter()
      })

      setTimeout(() => {
        pool.connect((err, client, releaseInner) => {
          expect(err).to.be(undefined)
          expect(client).to.not.be(undefined)
          releaseInner()
          pool.end(done)
        })
      }, 100)
    })
  })

  it('should timeout on query if all clients are busy', (done) => {
    const pool = new Pool({ connectionTimeoutMillis: 100, max: 1 })
    pool.connect((err, client, release) => {
      expect(err).to.be(undefined)
      expect(client).to.not.be(undefined)
      pool.query('select now()', (err, result) => {
        expect(err).to.be.an(Error)
        expect(result).to.be(undefined)
        release()
        pool.end(done)
      })
    })
  })

  it('should recover from timeout errors', (done) => {
    const pool = new Pool({ connectionTimeoutMillis: 100, max: 1 })
    pool.connect((err, client, release) => {
      expect(err).to.be(undefined)
      expect(client).to.not.be(undefined)
      pool.query('select now()', (err, result) => {
        expect(err).to.be.an(Error)
        expect(result).to.be(undefined)
        release()
        pool.query('select $1::text as name', ['brianc'], (err, res) => {
          expect(err).to.be(undefined)
          expect(res.rows).to.have.length(1)
          pool.end(done)
        })
      })
    })
  })

  it('continues processing after a connection failure', (done) => {
    const Client = require('pg').Client
    const orgConnect = Client.prototype.connect
    let called = false

    Client.prototype.connect = function (cb) {
      // Simulate a failure on first call
      if (!called) {
        called = true

        return setTimeout(() => {
          cb(connectionFailure)
        }, 100)
      }
      // And pass-through the second call
      orgConnect.call(this, cb)
    }

    const pool = new Pool({
      Client: Client,
      connectionTimeoutMillis: 1000,
      max: 1,
    })

    pool.connect((err, client, release) => {
      expect(err).to.be(connectionFailure)

      pool.query('select $1::text as name', ['brianc'], (err, res) => {
        expect(err).to.be(undefined)
        expect(res.rows).to.have.length(1)
        pool.end(done)
      })
    })
  })

  it('releases newly connected clients if the queued already timed out', (done) => {
    const Client = require('pg').Client

    const orgConnect = Client.prototype.connect

    let connection = 0

    Client.prototype.connect = function (cb) {
      // Simulate a failure on first call
      if (connection === 0) {
        connection++

        return setTimeout(() => {
          cb(connectionFailure)
        }, 300)
      }

      // And second connect taking > connection timeout
      if (connection === 1) {
        connection++

        return setTimeout(() => {
          orgConnect.call(this, cb)
        }, 1000)
      }

      orgConnect.call(this, cb)
    }

    const pool = new Pool({
      Client: Client,
      connectionTimeoutMillis: 1000,
      max: 1,
    })

    // Direct connect
    pool.connect((err, client, release) => {
      expect(err).to.be(connectionFailure)
    })

    // Queued
    let called = 0
    pool.connect((err, client, release) => {
      // Verify the callback is only called once
      expect(called++).to.be(0)
      expect(err).to.be.an(Error)

      pool.query('select $1::text as name', ['brianc'], (err, res) => {
        expect(err).to.be(undefined)
        expect(res.rows).to.have.length(1)
        pool.end(done)
      })
    })
  })
})