Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support timing out acquire() call #127

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
64 changes: 59 additions & 5 deletions lib/generic-pool.js
Expand Up @@ -365,25 +365,76 @@ Pool.prototype._ensureMinimum = function _ensureMinimum () {
}
}

Pool.prototype._isFull = function () {
return this._count >= this._factory.max && this._availableObjects.length === 0
}

/**
* Request a new client. The callback will be called,
* when a new client will be availabe, passing the client to it.
* when a new client will be available, passing the client to it.
*
* @param {Function} callback
* Callback function to be called after the acquire is successful.
* The function will receive the acquired item as the first parameter.
*
* @param {Number} priority
* Optional. Integer between 0 and (priorityRange - 1). Specifies the priority
* of the caller if there are no available resources. Lower numbers mean higher
* priority.
* Optional. Integer between 0 and (priorityRange - 1). Specifies the
* priority of the caller if there are no available resources. Lower numbers
* mean higher priority.
*
* @param {Object} options
* Optional. If `options.timeout` is set to a non-negative number, throw an
* error if we cannot acquire a client in `options.timeout` ms. If
* `options.timeout` is 0, we will hit the callback in the next tick if no
* clients are available. If no clients are available in the specified time,
* generic-pool will return Pool.full.
*
* Omit the timeout or set it to `null` to wait indefinitely.
*
* @returns {boolean} `true` if the pool is not fully utilized, `false` otherwise.
*/
Pool.prototype.acquire = function acquire (callback, priority) {
Pool.prototype.acquire = function acquire (callback, priority, options) {
if (this._draining) {
throw new Error('pool is draining and cannot accept work')
}
var that = this
if (options !== null && typeof options !== 'undefined') {
if (typeof options.timeout === 'number') {
if (options.timeout < 0 || isNaN(options.timeout)) {
callback(new Error('Timeout set to negative or invalid value: ' + options.timeout))
return false
} else if (options.timeout === 0) {
if (this._isFull()) {
process.nextTick(function () {
callback(fullError)
})
return false
}
} else {
var fired = false
var canceled = false
// This would break if Node ever allowed multiple threads, since
// there's a race between checking (fired) and hitting the callback.
var cbCopy = callback
callback = function (err, obj) {
if (canceled) {
that.release(obj)
return
}
fired = true
cbCopy(err, obj)
}
setTimeout(function () {
if (fired) {
return
} else {
canceled = true
cbCopy(fullError)
}
}, options.timeout)
}
}
}
this._waitingClients.enqueue(callback, priority)
this._dispense()
return (this._count < this._factory.max)
Expand Down Expand Up @@ -562,4 +613,7 @@ Pool.prototype.getMinPoolSize = function getMinPoolSize () {
return this._factory.min
}

var fullError = new Error('Cannot acquire resource because the pool is full')

exports.full = fullError
exports.Pool = Pool
276 changes: 276 additions & 0 deletions test/generic-pool.test.js
Expand Up @@ -461,6 +461,282 @@ module.exports = {
})
},

'timeout:0 returns error if pool full': function (beforeExit) {
var assertion_count = 0
var pool = poolModule.Pool({
name: 'block-false',
create: function (callback) { callback({id: Math.floor(Math.random() * 1000)}) },
destroy: function (client) {},
max: 2,
idleTimeoutMillis: 100
})
pool.acquire(function (err, obj1) {
if (err) { throw err }
pool.acquire(function (err, obj2) {
if (err) { throw err }
pool.acquire(function (err) {
assert.equal(err.message, 'Cannot acquire resource because the pool is full')
assertion_count += 1
assert.equal(err, poolModule.full)
assertion_count += 1
}, 0, {timeout: 0})
}, 0, {timeout: 0})
})

beforeExit(function () {
assert.equal(assertion_count, 2)
})
},

'timeout:5 times out if pool full': function (beforeExit) {
var released = false
var assertion_count = 0
var createCount = 0
var pool = poolModule.Pool({
name: 'timeout5',
create: function (callback) {
callback(null, { count: ++createCount })
},
destroy: function (client) {},
max: 2,
idleTimeoutMillis: 100
})
pool.acquire(function (err, obj1) {
if (err) { throw err }
pool.acquire(function (err, obj2) {
if (err) { throw err }
setTimeout(function () {
pool.release(obj1)
released = true
}, 10)
pool.acquire(function (err, obj3) {
assert.equal(err, poolModule.full)
assertion_count += 1
}, 0, {timeout: 5})
}, 0, {timeout: 0})
})

beforeExit(function () {
assert(released)
assert.equal(assertion_count, 1)
})
},

// 0. Set poolSize to 2
// 1. Acquire an object
// 2. Acquire 2nd object (resource created)
// 3. Release one object (all resources created, only one live)
// 4. Acquire an object (should succeed despite resources being created)
'timeout:0 returns error if all objects created, pool not full': function (beforeExit) {
var assertion_count = 0
var pool = poolModule.Pool({
name: 'test-object-created',
create: function (callback) { callback({id: Math.floor(Math.random() * 1000)}) },
destroy: function (client) {},
max: 2,
idleTimeoutMillis: 100
})
pool.acquire(function (err, obj1) {
if (err) { throw err }
pool.acquire(function (err, obj2) {
if (err) { throw err }
pool.release(obj1)
pool.acquire(function (err, obj3) {
assert.equal(err, null)
assertion_count += 1
}, 0, {timeout: 0})
}, 0, {timeout: 0})
})

beforeExit(function () {
assert.equal(assertion_count, 1)
})
},

'timeout:5 times out at the timeout time, not the release time': function (beforeExit) {
var released = false
var assertion_count = 0
var createCount = 0
var pool = poolModule.Pool({
name: 'timeout5',
create: function (callback) {
callback(null, { count: ++createCount })
},
destroy: function (client) {},
max: 2,
idleTimeoutMillis: 100
})
pool.acquire(function (err, obj1) {
if (err) { throw err }
pool.acquire(function (err, obj2) {
if (err) { throw err }
setTimeout(function () {
pool.release(obj1)
released = true
}, 50)
var start = Date.now()
pool.acquire(function (err, obj3) {
// time out times aren't exact, but let's check this is closer to
// 5 than 50
var diff = Date.now() - start
assert(diff < 25, 'expected time difference to be < 25, was ' + diff)
assertion_count += 1
assert.equal(err, poolModule.full)
assertion_count += 1
}, 0, {timeout: 5})
}, 0, {timeout: 0})
})

beforeExit(function () {
assert(released)
assert.equal(assertion_count, 2)
})
},

'timeout:5 lets you acquire more resources afterwards': function (beforeExit) {
var released = false
var assertion_count = 0
var createCount = 0
var pool = poolModule.Pool({
name: 'timeout5-release',
create: function (callback) {
callback(null, { count: ++createCount })
},
destroy: function (client) {},
max: 2,
idleTimeoutMillis: 100
})
pool.acquire(function (err, obj1) {
if (err) { throw err }
pool.acquire(function (err, obj2) {
if (err) { throw err }
setTimeout(function () {
pool.release(obj1)
released = true
}, 30)
pool.acquire(function (err, obj3) {
assert.equal(err, poolModule.full)
assertion_count += 1
pool.acquire(function (err, obj4) {
assert.equal(err, null)
assertion_count += 1
assert.equal(obj4.count, 1)
assertion_count += 1
})
}, 0, {timeout: 5})
}, 0, {timeout: 0})
})

beforeExit(function () {
assert(released)
assert.equal(assertion_count, 3)
})
},

'timeout: NaN returns an error': function (beforeExit) {
var assertion_count = 0
var createCount = 0
var pool = poolModule.Pool({
name: 'timeout5',
create: function (callback) {
callback(null, { count: ++createCount })
},
destroy: function (client) {},
max: 2
})
pool.acquire(function (err, obj1) {
assert(err.message, 'Timeout set to immediate or negative value: NaN')
assertion_count++
}, 0, {timeout: NaN})
beforeExit(function () {
assert.equal(assertion_count, 1)
})
},

'timeout: -1 returns an error': function (beforeExit) {
var assertion_count = 0
var createCount = 0
var pool = poolModule.Pool({
name: 'timeout5',
create: function (callback) {
callback(null, { count: ++createCount })
},
destroy: function (client) {},
max: 2
})
pool.acquire(function (err, obj1) {
assert(err.message, 'Timeout set to immediate or negative value: -1')
assertion_count++
}, 0, {timeout: -1})
beforeExit(function () {
assert.equal(assertion_count, 1)
})
},

'timeout:5 returns resource if pool isnt full': function (beforeExit) {
var assertion_count = 0
var createCount = 0
var pool = poolModule.Pool({
name: 'timeout5',
create: function (callback) {
callback(null, { count: ++createCount })
},
destroy: function (client) {},
max: 2,
idleTimeoutMillis: 100
})
pool.acquire(function (err, obj1) {
if (err) { throw err }
pool.acquire(function (err, obj2) {
if (err) { throw err }
assert.equal(err, null)
assertion_count += 1
assert.equal(obj2.count, 2)
assertion_count += 1
}, 0, {timeout: 5})
})

beforeExit(function () {
assert.equal(assertion_count, 2)
})
},

'timeout:5 returns resource if it becomes available before timeout': function (beforeExit) {
var released = false
var assertion_count = 0
var createCount = 0
var pool = poolModule.Pool({
name: 'timeout5',
create: function (callback) {
callback(null, { count: ++createCount })
},
destroy: function (client) {},
max: 2,
idleTimeoutMillis: 100
})
pool.acquire(function (err, obj1) {
if (err) { throw err }
pool.acquire(function (err, obj2) {
if (err) { throw err }
setTimeout(function () {
pool.release(obj1)
released = true
}, 10)
pool.acquire(function (err, obj3) {
assert.equal(err, null)
assertion_count += 1
assert.equal(obj3.count, 1)
assertion_count += 1
}, 0, {timeout: 500})
}, 0, {timeout: null})
})

beforeExit(function () {
assert(released)
assert.equal(assertion_count, 2)
})
},

'availableObjectsCount': function (beforeExit) {
var assertion_count = 0
var pool = poolModule.Pool({
Expand Down