
Security News
The Next Open Source Security Race: Triage at Machine Speed
Claude Opus 4.6 has uncovered more than 500 open source vulnerabilities, raising new considerations for disclosure, triage, and patching at scale.
@microfleet/ioredis-lock
Advanced tools

Node distributed locking using redis with lua scripts. Compatible with redis >= 2.6.12. A better alternative to locking strategies based on SETNX or WATCH/MULTI. Refer to Implementation and Alternatives for details.
Using npm, you can install redislock with npm i @microfleet/ioredis-lock -S.
redislock offers both atomic acquire and release operations, avoiding race conditions among clients, as well as the need for lock-specific redis connections. Lock creation requires a node_redis client, and accepts an object specifying the following three options:
import Bluebird from 'bluebird'
import { Redis } from 'ioredis'
import { createLock } from '@microfleet/ioredis-lock'
const client = new Redis();
const lock = createLock(client, {
timeout: 20000,
retries: 3,
delay: 100,
});
// this uses bind feature of `bluebird`
Bluebird
.bind(lock)
.call('acquire', 'app:feature:lock')
.catch(err => {
// handle err
})
.call('release')
.catch(err => {
// handle err
})
.then(() => {
// all good
});
Supports promises, thanks to bluebird, out of the box:
import Bluebird from 'bluebird'
import { Redis } from 'ioredis'
import { createLock, LockAcquisitionError, LockReleaseError } from '@microfleet/ioredis-lock'
const client = new Redis();
const lock = createLock(client);
Bluebird
.resolve(lock.acquire('app:feature:lock'))
.then(() => {
// Lock has been acquired
return lock.release();
}).then(() => {
// Lock has been released
}).catch(LockAcquisitionError, (err) => {
// The lock could not be acquired
}).catch(LockReleaseError, (err) => {
// The lock could not be released
});
Locking is performed using the following redis command:
SET key uuid PX timeout NX
If the SET returns OK, the lock has been acquired on the given key, and an expiration has been set. Then, releasing a lock uses the following redis script:
if redis.call('GET', KEYS[1]) == ARGV[1] then
return redis.call('DEL', KEYS[1])
end
return 0
This ensures that the key is deleted only if it is currently holding the lock, by passing its UUID as an argument. Extending a lock is done with a similar lua script:
if redis.call('GET', KEYS[1]) == ARGV[1] then
return redis.call('PEXPIRE', KEYS[1], ARGV[2])
end
return 0
Some alternative locking implementations do not use a random identifier, but
instead simply invoke SETNX, assigning a timestamp. This has the problem of
requiring synchronization of clocks between all instances to maintain timeout
accuracy. Furthermore, freeing a lock with such an implementation may risk
deleting a key set by a different lock.
Another technique used is to WATCH the key for changes when freeing,
achieving a CAS-like operation, as described below:
WATCH key # Begin watching the key for changes
GET key # Retrieve its value, return an error if not equal to the lock's UUID
MULTI # Start transaction
DEL key # Delete the key
EXEC # Execute the transaction, which will fail if the key had expired
However, this has the issue of requiring that you use a 1:1 mapping of redis
clients to locks to ensure that a competing MULTI is not invoked, and that
the release is unaffected by other watched keys.
In addition to the above, most locking libraries aren't compatible with promises
by default, and due to their API, require "promisifying" individual locks.
redislock avoids this issue by taking advantage of bluebird's nodeify
function to offer an API that easily supports both callbacks and promises.
The module exports three functions for lock creation and management, as well as two errors for simplified error handling when using promises.
Creates and returns a new Lock instance, configured for use with the supplied redis client, as well as options, if provided. The options object may contain following three keys, as outlined at the start of the documentation: timeout, retries and delay.
const lock = redislock.createLock(client, {
timeout: 10000,
retries: 3,
delay: 100
})
Returns an array of currently active/acquired locks.
// Create 3 locks, but only acquire 2
redislock.createLock(client);
await redislock.createLock(client).acquire('app:lock1')
await redislock.createLock(client).acquire('app:lock2')
const locks = redislock.getAcquiredLocks(); // [lock, lock]
The constructor for a LockAcquisitionError. Thrown or returned when a lock could not be acquired.
The constructor for a LockReleaseError. Thrown or returned when a lock could not be released.
The constructor for a LockExtendError. Thrown or returned when a lock could not be extended.
The lock class exposed by redislock. Each instance is assigned a UUID v1 string as an id, and is configured to work with the given redis client
Attempts to acquire a lock, given a key, and an optional callback function. If the initial lock fails, additional attempts will be made for the configured number of retries, and padded by the delay. The callback is invoked with an error on failure, and returns a promise if no callback is supplied. If invoked in the context of a promise, it may throw a LockAcquisitionError.
const lock = redislock.createLock(client);
try {
await lock.acquire('example:lock')
} catch (err) {
console.log(err.message); // 'Lock already held'
}
Attempts to release the lock, and accepts an optional callback function. The callback is invoked with an error on failure, and returns a promise if no callback is supplied. If invoked in the context of a promise, it may throw a LockReleaseError.
import { setTimeout } from 'node:timers/promises'
const lock = redislock.createLock(client)
try {
await lock.acquire('app:lock')
await setTimeout(20e3)
try {
await lock.release();
} catch (err) {
console.log(err.message); // 'Lock on app:lock has expired'
}
} catch (err) {
throw err
}
Attempts to extend the timeout of a lock, and accepts an optional callback function. The callback is invoked with an error on failure, and returns a promise if no callback is supplied. If invoked in the context of a promise, it may throw a LockExtendError.
const lock = redislock.createLock(client);
await lock.acquire('app:lock')
await setTimeout(20e3)
try {
await lock.extend(20000)
} catch (err) {
console.log(err.message); // 'Lock on app:lock has expired'
}
Unit and functional tests are available in the base spec directory, and can
be ran using npm test. Additional integration tests, which require an active
redis-server configured on the default port and host, can be ran using
mocha spec/integration/. Both tests suites are ran as part of the Travis CI
build thanks to their support for services such as redis.
FAQs
Node distributed locking using redis with ioredis adapter
The npm package @microfleet/ioredis-lock receives a total of 4,348 weekly downloads. As such, @microfleet/ioredis-lock popularity was classified as popular.
We found that @microfleet/ioredis-lock demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Claude Opus 4.6 has uncovered more than 500 open source vulnerabilities, raising new considerations for disclosure, triage, and patching at scale.

Research
/Security News
Malicious dYdX client packages were published to npm and PyPI after a maintainer compromise, enabling wallet credential theft and remote code execution.

Security News
gem.coop is testing registry-level dependency cooldowns to limit exposure during the brief window when malicious gems are most likely to spread.