Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,19 @@ const pool = new Pool({
// rejected with the error. If this is false (the default) then
// create is retried until acquireTimeoutMillis milliseconds has
// passed.
propagateCreateError: false
propagateCreateError: false,

// Order in which free resources are handed back out on acquire.
// false (the default) is LIFO: the most recently released resource
// is reused first, keeping a small hot set active and letting colder
// resources age out via idleTimeoutMillis.
// true is FIFO (round-robin): the least recently used free resource
// is reused first, cycling evenly across all resources.
//
// Note: with fifo: true, connections are rotated evenly, so idle
// connections rarely reach idleTimeoutMillis; the pool shrinks toward
// min far less aggressively than with the default LIFO.
fifo: false
});

// acquires a resource. The promise is rejected with `tarn.TimeoutError`
Expand Down
8 changes: 6 additions & 2 deletions src/Pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface PoolOptions<T> {
log?: (msg: string) => any;
validate?: (resource: T) => boolean;
propagateCreateError?: boolean;
fifo?: boolean;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually went to use this via patch-package somewhere and noticed a warning:

Pool config option "fifo" is no longer supported. See https://github.com/Vincit/tarn.js for possible pool config options.

Due to checking for options from the old pooling and warning

So maybe better to go with the more explicit freeResourceSelectionAlgorithm

@igalklebanov igalklebanov Jul 11, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More explicit definitely better here.

Personally,
I'm vibing more with "idle" than "free". "free" is an overloaded term that can confuse - in this context, to the point where someone thinks this is related to the freeing of resources.
And with "strategy" more than "algorithm".

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could later, if necessary, allow passing a custom strategy, or add more built-ins like "round-robin".

}

export class Pool<T> {
Expand All @@ -33,6 +34,7 @@ export class Pool<T> {
protected interval: ReturnType<typeof setInterval> | null;
protected destroyed = false;
protected propagateCreateError: boolean;
protected fifo: boolean;
protected idleTimeoutMillis: number;
protected createRetryIntervalMillis: number;
protected reapIntervalMillis: number;
Expand Down Expand Up @@ -119,7 +121,8 @@ export class Pool<T> {
idleTimeoutMillis: true,
reapIntervalMillis: true,
createRetryIntervalMillis: true,
propagateCreateError: true
propagateCreateError: true,
fifo: true
};

for (const key of Object.keys(opt)) {
Expand All @@ -140,6 +143,7 @@ export class Pool<T> {
this.reapIntervalMillis = opt.reapIntervalMillis || 1000;
this.createRetryIntervalMillis = opt.createRetryIntervalMillis || 200;
this.propagateCreateError = !!opt.propagateCreateError;
this.fifo = !!opt.fifo;

this.min = opt.min;
this.max = opt.max;
Expand Down Expand Up @@ -400,7 +404,7 @@ export class Pool<T> {
// on validation result to either leave the free resource to used resources array
// or destroy the free resource if validation did fail.
const pendingAcquire = this.pendingAcquires.shift();
const free = this.free.pop();
const free = this.fifo ? this.free.shift() : this.free.pop();

if (free === undefined || pendingAcquire === undefined) {
const errMessage = 'this.free was empty while trying to acquire resource';
Expand Down
47 changes: 47 additions & 0 deletions tests.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2362,6 +2362,53 @@ describe('Tarn', () => {
return Math.round(Math.random() * max);
}
});

describe('reuse order (fifo option)', () => {
function makePool(opt) {
let created = 0;
return new Pool({
create() {
return Promise.resolve({ id: created++ });
},
destroy() {},
min: 0,
max: 3,
...opt
});
}

it('reuses the most recently released resource by default (LIFO)', async () => {
pool = makePool();

const r0 = await pool.acquire().promise;
const r1 = await pool.acquire().promise;
const r2 = await pool.acquire().promise;
expect([r0.id, r1.id, r2.id]).to.eql([0, 1, 2]);

pool.release(r0);
pool.release(r1);
pool.release(r2);

const reused = await pool.acquire().promise;
expect(reused.id).to.equal(2);
});

it('reuses the least recently released resource when fifo is true (round-robin)', async () => {
pool = makePool({ fifo: true });

const r0 = await pool.acquire().promise;
const r1 = await pool.acquire().promise;
const r2 = await pool.acquire().promise;
expect([r0.id, r1.id, r2.id]).to.eql([0, 1, 2]);

pool.release(r0);
pool.release(r1);
pool.release(r2);

const reused = await pool.acquire().promise;
expect(reused.id).to.equal(0);
});
});
});

function sortBy(arr, key) {
Expand Down
Loading