Skip to content

Instantly share code, notes, and snippets.

@olalonde
Created August 10, 2016 21:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save olalonde/2cd3e1a6ea54454da16878e5cfb03a9f to your computer and use it in GitHub Desktop.
Save olalonde/2cd3e1a6ea54454da16878e5cfb03a9f to your computer and use it in GitHub Desktop.
Generic resource pool
const debug = initDebug('generic-pool')
const removeResource = (arr, resource) => {
const index = arr.findIndex((ele) => ele.resource === resource)
return arr.splice(index, 1)[0]
}
/**
* getResource: promise return function, called on acquire
* max (optional): total maximum resources in pool
* ttl (optional): time to live, time in ms before a resource removed from pool
*/
const initGenericPool = ({ max = 10, getResource, ttl } = {}) => {
// all "free" resources
const freePool = []
// all resources that were acquired but not released
const inusePool = []
// callbacks waiting for free resources
const waitingQueue = []
// Track resources that are currenlty fetched, counts towards
// pool size
let inflightCount = 0
const freeSize = () => freePool.length
const inuseSize = () => inusePool.length + inflightCount
const poolSize = () => freeSize() + inuseSize()
const poolIsFull = () => poolSize() === max
const stats = () => ({
max,
free: freeSize(),
inuse: inuseSize(),
wait: waitingQueue.length,
})
const waitInQueue = () => new Promise((resolve, reject) => {
debug('waitInQueue')
debug(stats())
waitingQueue.push([resolve, reject])
})
const nextInQueue = () => {
// debug('nextInQueue')
if (waitingQueue.length) {
const [resolve] = waitingQueue.pop()
resolve()
}
}
const isExpired = ({ createdAt }) => {
if (!Number.isInteger(ttl)) return false
return Date.now() > (createdAt + ttl)
}
const removeExpired = () => {
freePool.filter(isExpired).forEach((obj) => {
debug(`${obj.resource} expired`)
removeResource(freePool, obj.resource)
})
}
const acquire = () => {
debug('acquire')
removeExpired()
debug(stats())
// free resource available
if (freeSize() > 0) {
debug('free resource available')
const obj = freePool.pop()
inusePool.push(obj)
return Promise.resolve(obj.resource)
}
// no free resource and pool is full
if (poolIsFull()) {
debug('no free resource and pool is full')
return waitInQueue().then(() => acquire())
}
debug('no free resource and pool is not full')
// no free resource and pool is not full
inflightCount++
return getResource().then((resource) => {
inflightCount--
inusePool.push({ resource, createdAt: Date.now() })
return resource
}).catch((err) => {
inflightCount--
throw err
})
}
const release = (resource) => {
debug('release')
const obj = removeResource(inusePool, resource)
freePool.push(obj)
removeExpired()
nextInQueue()
}
const withResource = (promiseFn) => (
acquire()
.then((resource) => (
Promise.resolve()
.then(() => promiseFn(resource))
.then((result) => {
release(resource)
return result
})
.catch((err) => {
release(resource)
throw err
})
))
)
return { acquire, release, stats, withResource }
}
/*
example usage:
const pool = initGenericPool({
getResource: () => getPgClient(),
max: 5,
ttl: 10 * 60 * 1000, // 10 min
})
pool.withResource((pgClient) => {
// do something
console.log('acquired client')
}).then(() => {
console.log('released client')
})
}
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment