# tarn puts a timeout on every step of acquiring a pooled resource

> tarn is an MIT-licensed Node.js resource pool from Vincit that gives every stage of the acquire lifecycle its own timeout, so a hung create cannot fill the pool with resources that will never work. It is small, it has one runtime dependency, and the version in the package manifest is 3.0.1 from 2020.

**Vincit/tarn.js** — Simple and robust resource pool for node.js

- Repository: https://github.com/Vincit/tarn.js
- Stars: 520 · Forks: 43
- Language: JavaScript
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/vincit-tarn-js

## Why another pool, according to the project

The README opens with the question directly: why yet another resource pool. The answer it gives is that Tarn is focused on resilience and the ability to recover from errors, and that it has timeouts for all operations that can fail or timeout so that you should never end up with a pool full of crap. That phrase is doing the engineering work in this library. A pool without per-stage timeouts will happily keep creating resources that never become usable, because the create call itself is the thing that hangs, and the pool cannot tell the difference between a slow create and a dead one. The README also says Tarn has a comprehensive test suite, that the maintainers are committed to adding tests and fixing all the bugs that are found, and that Tarn will always remain simple. The project is from Vincit, authored by Sami Koskimäki, and the licence is MIT.

## Installing it and the shape of the package

The install is one command:

```bash
npm install tarn
```

The package is named tarn at version 3.0.1, with the main entry at dist/tarn.js and TypeScript declarations at dist/tarn.d.ts, and the published files are the README, the licence and the dist directory. The engines field asks for Node 8.0.0 or newer, and the only runtime dependency is @types/node, with everything else a development dependency: TypeScript 3.8.3 for the build, mocha and expect.js for the test suite, eslint with the TypeScript parser and plugin plus prettier for linting and formatting, husky and lint-staged for the pre-commit hook, and bluebird. The source is TypeScript in src/ with the test suite in a single tests.js, the build is a plain tsc, and the tests run through mocha with the slow and timeout flags set. The repository also carries a .circleci directory and a .travis.yml, so the continuous integration setup is from the period rather than the newer GitHub Actions arrangement.

## The four callbacks that define the pool

A pool is described by four functions, and the example in the README spells out the contract for each. The create function makes a resource, and you may either pass the resource to the callback in the form callback(error, resource) or return a promise that resolves it, but not both, and the callback syntax will be deprecated at some point. The validate function checks a connection before it is used and returns true or false, and if it returns false the resource is destroyed and another one acquired, and it may return a promise if validation is asynchronous. The destroy function cleans a resource up and should return a promise if destroying is asynchronous. And the log function takes a message and a log level, and is a no-op by default. The example passes min 2 and max 10, so the pool holds a floor and a ceiling of resources, and it is worth noticing that the four callbacks are the entire configuration surface for behaviour, with everything else being about timing.

## Every timeout, and what it protects

The timing options are the reason the library exists, and each one is a separate failure mode. acquireTimeoutMillis is 30000 in the example, and it rejects the acquire promise after that many milliseconds if a resource cannot be acquired, with a TimeoutError. createTimeoutMillis is also 30000, and it cancels create operations after that period. destroyTimeoutMillis is 5000, and destroy operations are awaited for at most that long, with new resources created after the timeout rather than waiting for a destroy that will never finish. idleTimeoutMillis is 30000 and destroys free resources after that period, with the explicit note that if min is greater than zero some resources may be kept alive for longer, so to reliably destroy all idle resources you set min to 0. reapIntervalMillis is 1000, the frequency of the check for idle resources to destroy. And createRetryIntervalMillis is 200, how long to idle after a failed create before trying again.

## What happens when creation keeps failing

The propagateCreateError option decides whether a failed create is reported to the caller or absorbed. The README explains both branches: if it is true, when a create fails the first pending acquire is rejected with the error, and if it is false, which is the default, creation is retried until acquireTimeoutMillis has passed. The default is therefore the forgiving one, and the price of it is that a caller waiting on a broken backend waits for the full acquire timeout rather than learning immediately that the backend is down. The example sets propagateCreateError to false explicitly, which is a useful thing to see because it makes the choice visible rather than implicit. The two policies suit different systems, and a service that wants to shed load fast and one that wants to ride out a brief outage make opposite calls here.

## Acquiring, aborting and releasing

The usage example shows the acquire path in full, including the parts that are easy to get wrong. You call pool.acquire() and get an acquire object, which has a promise property that resolves with the resource, and a catch branch that checks whether the error is an instance of TimeoutError. The abort method cancels an acquire that has not been satisfied, and the README is precise about the limit: if the acquire had already triggered creating a new resource, the creation continues and is not aborted, so aborting is about the wait rather than about the work already in flight. When you are done, pool.release(resource) returns the resource to the pool. The pool also reports its own state, with numUsed for the number of resources not free, numFree for the free ones, numPendingAcquires for the acquires waiting for a release, and numPendingCreates for the asynchronous create calls currently running. And pool.destroy() waits for all resources to be returned to the pool and destroys them, after which the pool cannot be used.

## The event hooks and the changelog

The pool emits events, and the README lists them because they are the intended way to collect diagnostic data about pool behaviour. The acquire side has acquireRequest, acquireSuccess and acquireFail, the release event fires when a resource comes back, and the create side has createRequest, createSuccess and createFail. The destroy side has destroyRequest, destroySuccess and destroyFail, where the resource may or may not be invalid by the time the outcome fires. The reaper has startReaping and stopReaping for the internal idle clock, and the pool has poolDestroyRequest and poolDestroySuccess, after which all the event handlers are cleared as well. The README notes that if any of these hooks fail, the errors are caught and warnings are logged rather than thrown. The changelog is short and shows how the API has moved: version 3.0.0 in April 2020 added async validation support and was released as a major version because the internal changes could cause subtle behavioural differences, and 3.0.1 in October 2020 added a missing createFail event on timeout errors.

## Where tarn is the wrong choice

Three cases. The first is a system that needs a pool to do more than hold resources, because tarn's job ends at acquire and release, and anything like routing work to the least loaded member or per-resource prioritisation is code you write on top. The second is a project that wants the newest Node and the newest TypeScript, because the package pins a TypeScript 3.8.3 for the build and an eslint 6 for linting, which are the versions of a period rather than of today, and the source will need attention before it sits happily in a modern toolchain. The third is a team that wants a feature request implemented, because there are no GitHub releases and the changelog's last entry is from 2020, so the project's willingness to add things is not something the repository promises. Against a general connection pool, the difference in approach is that tarn is deliberately narrow: four callbacks and a set of timeouts, with the whole library small enough to read, and the price of that narrowness is that anything more elaborate has to be built by you.

## Conclusion

tarn is a good fit for a Node service that hands out connections or clients and has been bitten by a pool that grows while every resource is broken, because the per-stage timeouts and the create retry policy address that failure directly and the whole library is small enough to read. It is a poor fit if you need a pool that does more than hold resources, such as one that routes work to the least loaded member, and it is a poor fit if you need a maintained release, because the package manifest still says 3.0.1 from 2020 while the last push was on 2026-06-23. Check three things before you adopt it: that your Node version satisfies the engines field, which asks for 8.0.0 or newer, that you can decide what happens when a create fails, since propagateCreateError false by default means creation is retried until the acquire timeout rather than failing the first waiter, and that you set min to zero if you need every idle resource destroyed reliably, because the idle timeout will keep resources alive beyond it when min is greater than zero.

## FAQ

### What is tarn and what problem does it solve?

It is a simple resource pool for Node.js from Vincit, focused on resilience and recovery from errors. It has timeouts for all operations that can fail or timeout, so you should never end up with a pool full of resources that do not work.

### How do I install tarn?

With npm install tarn. The package is MIT licensed, the main entry is dist/tarn.js with type declarations at dist/tarn.d.ts, and the engines field asks for Node 8.0.0 or newer. The only runtime dependency is @types/node.

### Which timeouts does tarn support?

acquireTimeoutMillis rejects the acquire promise after that delay, createTimeoutMillis cancels create operations, destroyTimeoutMillis bounds how long a destroy is awaited before new resources are created, idleTimeoutMillis destroys free resources, reapIntervalMillis sets how often idle resources are checked, and createRetryIntervalMillis is how long to wait after a failed create before trying again.

### What does propagateCreateError do in tarn?

If it is true, when a create fails the first pending acquire is rejected with the error. If it is false, which is the default, creation is retried until acquireTimeoutMillis milliseconds have passed, so a caller waits for the full acquire timeout instead of learning immediately that the backend is down.

### How do I reliably destroy all idle resources in tarn?

Set min to 0. The idle timeout will keep resources alive for longer than idleTimeoutMillis when min is greater than zero, and the README states explicitly that to reliably destroy all idle resources you set min to 0. The pool also has a destroy method that waits for all resources to be returned and destroys them, after which the pool cannot be used.

## Sources

- [Issues](https://github.com/Vincit/tarn.js/issues)
- [License: MIT](https://github.com/Vincit/tarn.js/blob/master/LICENSE)
- [README](https://github.com/Vincit/tarn.js/blob/master/README.md)
- [Vincit/tarn.js on GitHub](https://github.com/Vincit/tarn.js)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/vincit-tarn-js
