Created
August 10, 2016 21:43
-
-
Save olalonde/2cd3e1a6ea54454da16878e5cfb03a9f to your computer and use it in GitHub Desktop.
Generic resource pool
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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