Pool2
Pool2 is a generic resource pooling library for Node.js, designed to efficiently manage expensive, reusable resources like database connections or network sockets. It provides a configurable framework for acquiring, releasing, disposing, and destroying resources, incorporating essential features such as minimum and maximum pool sizes, idle timeouts, request queuing, and health checks (ping functions). Key differentiators include its robust handling of resource lifecycle events and explicit timeouts for acquire and dispose operations, which helps prevent resource leaks and ensure application stability. The package is currently at version 1.4.1. While a specific release cadence isn't defined, the documentation indicates it's a mature and stable solution for resource management, offering fine-grained control over resource behavior and pooling strategies.
Common errors
-
Error: Acquire Timeout Exceeded
cause The `acquire` function took longer than `acquireTimeout` to invoke its callback, or no resources were available within the `requestTimeout` and `maxRequests` limits. This does not necessarily mean the resource failed to connect, only that `pool2` stopped waiting.fixIncrease `acquireTimeout` if the resource truly takes longer to provision, or set `requestTimeout` if waiting for an available resource. More importantly, check if the underlying resource acquisition in your `acquire` function has its own timeouts and handles them by reporting failure to `pool2`'s callback. Also verify `max` pool size and `maxRequests` are adequate for your load. -
Application hangs or crashes unexpectedly due to too many open file descriptors or 'Socket already closed' errors.
cause The `dispose` function failed to properly close an underlying resource (e.g., a database connection), leading to a resource leak. While `pool2` removes the resource from its internal tracking, the external resource remains open and unmanaged.fixThoroughly review the `dispose` function implementation to ensure all necessary steps are taken to gracefully close the resource. Add logging for `dispose` failures to quickly identify and troubleshoot issues related to resource leakage. Consider integrating a linter or static analysis tool to catch unhandled promises or forgotten callbacks.
Warnings
- gotcha The `acquireTimeout` only dictates how long `pool2` waits for the `acquire` function to *finish* its callback. If the underlying resource acquisition (e.g., database connection) takes longer than `acquireTimeout` but eventually succeeds, `pool2` will have moved on, potentially leading to unmanaged 'in-flight' resources that exceed `max` capacity. It's crucial for the `acquire` function itself to implement and handle its own timeouts and clean up if it exceeds an internal limit.
- gotcha Failures in the `dispose` function, even if `disposeTimeout` is set, will still remove the resource from the pool. However, if the underlying resource's graceful closure fails, it may leave dangling sockets or open handles, preventing a graceful application exit or causing resource leaks outside of `pool2`'s control.
- gotcha The `destroy` function is 'fire-and-forget' and does not accept a callback. This means `pool2` does not wait for or guarantee the completion of the `destroy` logic. It's intended as a last-resort cleanup.
- gotcha Incorrectly configuring `min` and `max` pool sizes alongside `idleTimeout` and `syncInterval` can lead to unexpected pool behavior, such as premature resource destruction or excessive resource creation during low load, or connection pool exhaustion under high load if not sized correctly relative to the application's demands and database capacity.
Install
-
npm install pool2 -
yarn add pool2 -
pnpm add pool2
Imports
- Pool
import Pool from 'pool2';
const Pool = require('pool2');
Quickstart
const Pool = require('pool2');
// Simulate a resource that takes time to acquire and needs disposal
let resourceCounter = 0;
function createResource(id) {
return {
id: id,
status: 'open',
connect: () => new Promise(resolve => setTimeout(() => {
console.log(`Resource ${id} connected.`);
resolve();
}, 100)),
close: () => new Promise(resolve => setTimeout(() => {
console.log(`Resource ${id} closed.`);
resolve();
}, 50)),
destroy: () => {
console.log(`Resource ${id} forcibly destroyed.`);
}
};
}
const pool = new Pool({
acquire: function (cb) {
const id = ++resourceCounter;
const rsrc = createResource(id);
rsrc.connect()
.then(() => cb(null, rsrc))
.catch(err => cb(err));
},
acquireTimeout: 5000,
dispose: function (rsrc, cb) {
rsrc.close()
.then(() => cb())
.catch(err => cb(err));
},
disposeTimeout: 2000,
destroy: function (rsrc) {
if (rsrc && rsrc.status === 'open') {
rsrc.destroy();
}
},
ping: function (rsrc, cb) {
// Simple ping, assume resource is always alive for this example
cb();
},
min: 1,
max: 3,
idleTimeout: 3000,
syncInterval: 1000
});
async function usePool() {
console.log('Acquiring resource...');
let resource;
try {
resource = await new Promise((resolve, reject) => {
pool.acquire((err, r) => {
if (err) return reject(err);
resolve(r);
});
});
console.log(`Successfully acquired resource ${resource.id}. Doing work...`);
await new Promise(resolve => setTimeout(resolve, 500)); // Simulate work
} catch (err) {
console.error('Failed to acquire resource:', err.message);
return;
} finally {
if (resource) {
console.log(`Releasing resource ${resource.id}.`);
pool.release(resource);
}
}
console.log('Current pool stats:', pool.stats());
}
(async () => {
await usePool();
await usePool();
await usePool();
console.log('Ending pool in 5 seconds...');
setTimeout(() => {
pool.end((errs) => {
if (errs && errs.length > 0) {
console.error('Errors during pool end:', errs);
} else {
console.log('Pool ended gracefully.');
}
});
}, 5000);
})();