Security News
Research
Supply Chain Attack on Rspack npm Packages Injects Cryptojacking Malware
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
Redlock is a distributed lock manager for Node.js using Redis. It implements the Redlock algorithm, which is a method for achieving distributed mutual exclusion. This is useful for ensuring that only one process can access a shared resource at a time, even in a distributed system.
Acquiring a Lock
This feature allows you to acquire a lock on a resource. The lock is held for a specified duration (2000 ms in this example). If the lock is successfully acquired, you can perform your operations and then release the lock.
const Redlock = require('redlock');
const redis = require('redis');
const client = redis.createClient();
const redlock = new Redlock([client]);
redlock.lock('locks:resource', 2000).then(function(lock) {
// Do something with the lock
console.log('Lock acquired');
// Release the lock
return lock.unlock();
}).catch(function(err) {
console.error('Failed to acquire lock', err);
});
Extending a Lock
This feature allows you to extend the duration of an existing lock. This is useful if you need more time to complete your operations while holding the lock.
const Redlock = require('redlock');
const redis = require('redis');
const client = redis.createClient();
const redlock = new Redlock([client]);
redlock.lock('locks:resource', 2000).then(function(lock) {
// Extend the lock
return lock.extend(2000);
}).then(function(lock) {
console.log('Lock extended');
// Release the lock
return lock.unlock();
}).catch(function(err) {
console.error('Failed to extend lock', err);
});
Handling Lock Failures
This feature demonstrates how to handle failures when acquiring or maintaining a lock. If an error occurs, it is caught and handled appropriately.
const Redlock = require('redlock');
const redis = require('redis');
const client = redis.createClient();
const redlock = new Redlock([client]);
redlock.lock('locks:resource', 2000).then(function(lock) {
// Do something with the lock
console.log('Lock acquired');
// Simulate a failure
throw new Error('Something went wrong');
}).catch(function(err) {
console.error('Failed to acquire or maintain lock', err);
});
node-redlock is another implementation of the Redlock algorithm for Node.js. It provides similar functionality to the redlock package, allowing you to acquire, extend, and release distributed locks using Redis. The main difference is in the API design and some additional features like automatic retry mechanisms.
redis-lock is a simpler package for distributed locking using Redis. It provides basic lock and unlock functionality but does not implement the full Redlock algorithm. It is easier to use but may not be as robust in a highly distributed environment.
redlock-js is another package that implements the Redlock algorithm. It offers similar features to the redlock package, including acquiring, extending, and releasing locks. It also provides additional configuration options for customizing the lock behavior.
This is a node.js implementation of the redlock algorithm for distributed redis locks. It provides strong guarantees in both single-redis and multi-redis environments, and provides fault tolerance through use of multiple independent redis instances or clusters.
npm install --save redlock
Redlock is designed to use ioredis to keep its client connections and handle the cluster protocols.
A redlock object is instantiated with an array of at least one redis client and an optional options
object. Properties of the Redlock object should NOT be changed after it is first used, as doing so could have unintended consequences for live locks.
import Client from "ioredis";
import Redlock from "./redlock";
const redisA = new Client({ host: "a.redis.example.com" });
const redisB = new Client({ host: "b.redis.example.com" });
const redisC = new Client({ host: "c.redis.example.com" });
const redlock = new Redlock(
// You should have one client for each independent redis node
// or cluster.
[redisA, redisB, redisC],
{
// The expected clock drift; for more details see:
// http://redis.io/topics/distlock
driftFactor: 0.01, // multiplied by lock ttl to determine drift time
// The max number of times Redlock will attempt to lock a resource
// before erroring.
retryCount: 10,
// the time in ms between attempts
retryDelay: 200, // time in ms
// the max time in ms randomly added to retries
// to improve performance under high contention
// see https://www.awsarchitectureblog.com/2015/03/backoff.html
retryJitter: 200, // time in ms
// The minimum remaining time on a lock before an extension is automatically
// attempted with the `using` API.
automaticExtensionThreshold: 500, // time in ms
}
);
The using
method wraps and executes a routine in the context of an auto-extending lock, returning a promise of the routine's value. In the case that auto-extension fails, an AbortSignal will be updated to indicate that abortion of the routine is in order, and to pass along the encountered error.
await redlock.using([senderId, recipientId], 5000, async (signal) => {
// Do something...
await something();
// Make sure any attempted lock extension has not failed.
if (signal.aborted) {
throw signal.error;
}
// Do something else...
await somethingElse();
});
Alternatively, locks can be acquired and released directly:
// Acquire a lock.
let lock = await redlock.acquire(["a"], 5000);
try {
// Do something...
await something();
// Extend the lock.
lock = await lock.extend(5000);
// Do something else...
await somethingElse();
} finally {
// Release the lock.
await lock.release();
}
Because redlock is designed for high availability, it does not care if a minority of redis instances/clusters fail at an operation.
However, it can be helpful to monitor and log such cases. Redlock emits an "error" event whenever it encounters an error, even if the error is ignored in its normal operation.
redlock.on("error", (error) => {
// Ignore cases where a resource is explicitly marked as locked on a client.
if (error instanceof ResourceLockedError) {
return;
}
// Log all other errors.
console.error(error);
});
Additionally, a per-attempt and per-client stats (including errors) are made available on the attempt
propert of both Lock
and ExecutionError
classes.
Please view the (very concise) source code or TypeScript definitions for a detailed breakdown of the API.
Please see CONTRIBUTING.md
for information on developing, running, and testing this library.
Please make sure to use a client with built-in cluster support, such as ioredis.
It is completely possible to use a single redis cluster or sentinal configuration by passing one preconfigured client to redlock. While you do gain high availability and vastly increased throughput under this scheme, the failure modes are a bit different, and it becomes theoretically possible that a lock is acquired twice:
Assume you are using eventually-consistent redis replication, and you acquire a lock for a resource. Immediately after acquiring your lock, the redis master for that shard crashes. Redis does its thing and fails over to the slave which hasn't yet synced your lock. If another process attempts to acquire a lock for the same resource, it will succeed!
This is why redlock allows you to specify multiple independent nodes/clusters: by requiring consensus between them, we can safely take out or fail-over a minority of nodes without invalidating active locks.
To learn more about the the algorithm, check out the redis distlock page.
Also note that when acquiring a lock on multiple resources, commands are executed in a single call to redis. Redis clusters require that all keys exist in a command belong to the same node. If you are using a redis cluster or clusters and need to lock multiple resources together you MUST use redis hash tags (ie. use ignored{considered}ignored{ignored}
notation in resource strings) to ensure that all keys resolve to the same node. Chosing what data to include must be done thoughtfully, because representing the same conceptual resource in more than one way defeats the purpose of acquiring a lock. Accordingly, it's generally wise to use a single very generic prefix to ensure that ALL lock keys resolve to the same node, such as {redlock}my_resource
. This is the most straightforward strategy and may be appropriate when the cluster has additional purposes. However, when locks will always naturally share a common attribute (for example, an organization/tenant ID), this may be used for better key distribution and cluster utilization. You can also acheive ideal utilization by completely omiting a hash tag if you do not need to lock multiple resources at the same time.
The purpose of redlock is to provide exclusivity guarantees on a resource over a duration of time, and is not designed to report the ownership status of a resource. For example, if you are on the smaller side of a network partition you will fail to acquire a lock, but you don't know if the lock exists on the other side; all you know is that you can't guarantee exclusivity on yours. This is further complicated by retry behavior, and even moreso when acquiring a lock on more than one resource.
That said, for many tasks it's sufficient to attempt a lock with retryCount=0
, and treat a failure as the resource being "locked" or (more correctly) "unavailable".
Note that with retryCount=-1
there will be unlimited retries until the lock is aquired.
Beginning in version 5, this package is published as an ECMAScript module. While this is universally accepted as the format of the future, there remain some quirks when used in CommonJS node applications. To provide better erganomics for use in CommonJS projects, this package also distributes a CommonJS version. Please ensure that your project either uses the CommonJS or ECMAScript version but NOT both.
In version 6, this package will stop distributing a CommonJS version.
FAQs
A node.js redlock implementation for distributed redis locks
We found that redlock demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer 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
Research
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
Research
Security News
Socket researchers discovered a malware campaign on npm delivering the Skuld infostealer via typosquatted packages, exposing sensitive data.
Security News
Sonar’s acquisition of Tidelift highlights a growing industry shift toward sustainable open source funding, addressing maintainer burnout and critical software dependencies.