Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

redlock

Package Overview
Dependencies
Maintainers
1
Versions
27
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

redlock - npm Package Compare versions

Comparing version 4.2.0 to 5.0.0-alpha.0

dist/index.d.ts

7

CHANGELOG.md

@@ -17,1 +17,8 @@ ## v4.0.0

- Use evalsha for scripts (@yosiat via [#77](https://github.com/mike-marcacci/node-redlock/pull/77)).
## v5.0.0-alpha1
- Complete rewrite using TypeScript.
- **BREAKING** Significant API changes; see [README.md](./README.md)
- **BREAKING** Remove all production dependencies (replacing Bluebird with native promises).
- **BREAKING** Drop support for Node < 12

79

package.json
{
"name": "redlock",
"version": "4.2.0",
"version": "5.0.0-alpha.0",
"description": "A node.js redlock implementation for distributed redis locks",
"main": "redlock.js",
"scripts": {
"test": "istanbul cover mocha",
"test-ci": "istanbul cover _mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | coveralls"
"license": "MIT",
"author": {
"name": "Mike Marcacci",
"email": "mike.marcacci@gmail.com"
},
"repository": {
"type": "git",
"url": "https://github.com/mike-marcacci/node-redlock.git"
},
"repository": "https://github.com/mike-marcacci/node-redlock.git",
"homepage": "https://github.com/mike-marcacci/node-redlock#readme",
"bugs": "https://github.com/mike-marcacci/node-redlock/issues",
"main": "dist/index.js",
"keywords": [
"nodejs",
"iojs",
"redlock",

@@ -22,29 +21,45 @@ "distributed",

],
"author": "Mike Marcacci",
"license": "MIT",
"bugs": {
"url": "https://github.com/mike-marcacci/node-redlock/issues"
"files": [
"dist/index.d.ts",
"dist/index.js",
"dist/index.js.map"
],
"engines": {
"node": ">=12"
},
"homepage": "https://github.com/mike-marcacci/node-redlock",
"browserslist": "node >= 12",
"ava": {
"nodeArguments": [
"--experimental-specifier-resolution=node"
]
},
"devDependencies": {
"chai": "^4.2.0",
"coveralls": "^3.1.0",
"ioredis": "^4.18.0",
"istanbul": "^0.4.2",
"mocha": "^8.1.3",
"redis": "^3.0.2"
"@types/ioredis": "^4.26.6",
"@types/node": "^16.4.2",
"@typescript-eslint/eslint-plugin": "^4.28.4",
"@typescript-eslint/parser": "^4.28.4",
"ava": "^3.13.0",
"eslint": "^7.31.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.2.0",
"ioredis": "^4.19.2",
"nodemon": "^2.0.6",
"prettier": "^2.2.1",
"typescript": "^4.1.2"
},
"scripts": {
"format": "prettier --list-different --write '**/*.{json,yml,md,ts}'",
"lint": "prettier -c '**/*.{json,yml,md,ts}' && eslint src --ext ts",
"build": "rm -rf dist && tsc",
"build:development": "rm -rf dist && tsc --watch",
"test": "ava --verbose dist/*.test.js",
"test:development": "ava --verbose --watch dist/*.test.js",
"prepare": "yarn build",
"prepublishOnly": "yarn install && yarn lint && yarn build"
},
"dependencies": {
"bluebird": "^3.7.2"
"node-abort-controller": "^2.0.0"
},
"engines": {
"node": ">=8.0.0"
},
"config": {
"blanket": {
"pattern": [
"redlock.js"
]
}
}
"type": "module",
"exports": "./dist/index.js"
}

@@ -1,28 +0,24 @@

[![npm version](https://badge.fury.io/js/redlock.svg)](https://www.npmjs.com/package/redlock)
[![Build Status](https://travis-ci.org/mike-marcacci/node-redlock.svg)](https://travis-ci.org/mike-marcacci/node-redlock)
[![Coverage Status](https://coveralls.io/repos/mike-marcacci/node-redlock/badge.svg)](https://coveralls.io/r/mike-marcacci/node-redlock)
[![Continuous Integration](https://github.com/mike-marcacci/node-redlock/workflows/Continuous%20Integration/badge.svg)](https://github.com/mike-marcacci/node-redlock/actions/workflows/ci.yml)
[![Current Version](https://badgen.net/npm/v/redlock)](https://npm.im/redlock)
[![Supported Node.js Versions](https://badgen.net/npm/node/redlock)](https://npm.im/redlock)
Redlock
=======
# Redlock
This is a node.js implementation of the [redlock](http://redis.io/topics/distlock) 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.
- [Installation](#installation)
- [Usage (Promise Style)](#usage-promise-style)
- [Usage (Disposer Style)](#usage-disposer-style)
- [Usage (Callback Style)](#usage-callback-style)
- [Locking multiple resources](#locking-multiple-resources)
- [API Docs](#api-docs)
- [Usage](#usage)
### High-Availability Recommendations
- Use at least 3 independent servers or clusters
- Use an odd number of independent redis ***servers*** for most installations
- Use an odd number of independent redis ***clusters*** for massive installations
- Use an odd number of independent redis **_servers_** for most installations
- Use an odd number of independent redis **_clusters_** for massive installations
- When possible, distribute redis nodes across different physical machines
### Using Cluster/Sentinel
***Please make sure to use a client with built-in cluster support, such as [ioredis](https://github.com/luin/ioredis).***
**_Please make sure to use a client with built-in cluster support, such as [ioredis](https://github.com/luin/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:
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:

@@ -35,13 +31,12 @@ 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!

### How do I check if something is locked?
Redlock cannot tell you *with certainty* if a resource is currently locked. 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.
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",
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.
With `retryCount=-1` there will be unlimited retries until the lock is aquired.
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.
Installation
------------
## Installation
```bash

@@ -51,345 +46,104 @@ npm install --save redlock

Configuration
-------------
Redlock can use [node redis](https://github.com/mranney/node_redis), [ioredis](https://github.com/luin/ioredis) or any other compatible redis library to keep its client connections.
## Configuration
Redlock is designed to use [ioredis](https://github.com/luin/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.
```js
var client1 = require('redis').createClient(6379, 'redis1.example.com');
var client2 = require('redis').createClient(6379, 'redis2.example.com');
var client3 = require('redis').createClient(6379, 'redis3.example.com');
var Redlock = require('redlock');
```ts
import Client from "ioredis";
import Redlock from "./redlock";
var redlock = new Redlock(
// you should have one client for each independent redis node
// or cluster
[client1, client2, client3],
{
// the expected clock drift; for more details
// see http://redis.io/topics/distlock
driftFactor: 0.01, // multiplied by lock ttl to determine drift time
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" });
// the max number of times Redlock will attempt
// to lock a resource before erroring
retryCount: 10,
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 time in ms between attempts
retryDelay: 200, // time in ms
// The max number of times Redlock will attempt to lock a resource
// before erroring.
retryCount: 10,
// 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 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
Error Handling
--------------
Because redlock is designed for high availability, it does not care if a minority of redis instances/clusters fail at an operation. If you want to write logs or take another action when a redis client fails, you can listen for the `clientError` event:
```js
// ...
redlock.on('clientError', function(err) {
console.error('A redis error has occurred:', err);
});
// ...
// The minimum remaining time on a lock before an extension is automatically
// attempted with the `using` API.
automaticExtensionThreshold: 500, // time in ms
}
);
```
## Error Handling
Usage (promise style)
---------------------
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.
### Locking & Unlocking
```ts
redlock.on("error", (error) => {
// Ignore cases where a resource is explicitly marked as locked on a client.
if (error instanceof ResourceLockedError) {
return;
}
```js
// the string identifier for the resource you want to lock
var resource = 'locks:account:322456';
// the maximum amount of time you want the resource locked in milliseconds,
// keeping in mind that you can extend the lock up until
// the point when it expires
var ttl = 1000;
redlock.lock(resource, ttl).then(function(lock) {
// ...do something here...
// unlock your resource when you are done
return lock.unlock()
.catch(function(err) {
// we weren't able to reach redis; your lock will eventually
// expire, but you probably want to log this error
console.error(err);
});
// 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.
### Locking and Extending
## Usage
```js
redlock.lock('locks:account:322456', 1000).then(function(lock) {
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.
// ...do something here...
```ts
await redlock.using([senderId, recipientId], 5000, async (signal) => {
// Do something...
await something();
// if you need more time, you can continue to extend
// the lock as long as you never let it expire
// Make sure any necessary lock extension has not failed.
if (signal.aborted) {
throw signal.error;
}
// this will extend the lock so that it expires
// approximitely 1s from when `extend` is called
return lock.extend(1000).then(function(lock){
// ...do something here...
// unlock your resource when you are done
return lock.unlock()
.catch(function(err) {
// we weren't able to reach redis; your lock will eventually
// expire, but you probably want to log this error
console.error(err);
});
});
// Do something else...
await somethingElse();
});
```
Alternatively, locks can be acquired and released directly:
Usage (disposer style)
----------------------
```ts
// Acquire a lock.
let lock = await redlock.acquire(["a"], 5000);
// Do something...
await something();
### Locking & Unlocking
// Extend the lock.
lock = await lock.extend(5000);
```js
var using = require('bluebird').using;
// Do something else...
await somethingElse();
// the string identifier for the resource you want to lock
var resource = 'locks:account:322456';
// the maximum amount of time you want the resource locked,
// keeping in mind that you can extend the lock up until
// the point when it expires
var ttl = 1000;
// if we weren't able to reach redis, your lock will eventually
// expire, but you probably want to do something like log that
// an error occurred; if you don't pass a handler, this error
// will be ignored
function unlockErrorHandler(err) {
console.error(err);
}
using(redlock.disposer(resource, ttl, unlockErrorHandler), function(lock) {
// ...do something here...
}); // <-- unlock is automatically handled by bluebird
// Release the lock.
await lock.release();
```
## API
### Locking and Extending
```js
using(redlock.disposer('locks:account:322456', 1000, unlockErrorHandler), function(lock) {
// ...do something here...
// if you need more time, you can continue to extend
// the lock as long as you never let it expire
// this will extend the lock so that it expires
// approximitely 1s from when `extend` is called
return lock.extend(1000).then(function(extended){
// Note that redlock modifies the original lock,
// so the vars `lock` and `extended` point to the
// exact same object
// ...do something here...
});
}); // <-- unlock is automatically handled by bluebird
```
Usage (callback style)
----------------------
### Locking & Unlocking
```js
// the string identifier for the resource you want to lock
var resource = 'locks:account:322456';
// the maximum amount of time you want the resource locked,
// keeping in mind that you can extend the lock up until
// the point when it expires
var ttl = 1000;
redlock.lock(resource, ttl, function(err, lock) {
// we failed to lock the resource
if(err) {
// ...
}
// we have the lock
else {
// ...do something here...
// unlock your resource when you are done
lock.unlock(function(err) {
// we weren't able to reach redis; your lock will eventually
// expire, but you probably want to log this error
console.error(err);
});
}
});
```
### Locking and Extending
```js
redlock.lock('locks:account:322456', 1000, function(err, lock) {
// we failed to lock the resource
if(err) {
// ...
}
// we have the lock
else {
// ...do something here...
// if you need more time, you can continue to extend
// the lock as long as you never let it expire
// this will extend the lock so that it expires
// approximitely 1s from when `extend` is called
lock.extend(1000, function(err, lock){
// we failed to extend the lock on the resource
if(err) {
// ...
}
// ...do something here...
// unlock your resource when you are done
lock.unlock();
}
}
});
```
## Locking multiple resources
Multiple resources can be locked by providing an `Array` of strings to `Redlock.prototype.lock` call. Internally a single attempt is made to `redis` by evaluating script which executes lock statements. For more details about atomicity of scripts please see [redis reference](https://redis.io/commands/eval#atomicity-of-scripts).
There are however some limitations of which you need to be aware of:
- When requesting a lock it will fail if any of requested resources is already set
- If lock attempt fails for any resource (due to whatever reason) an attempt for removing already set resources is made. However there are no guarantees that it will succeed (`redis` doesn't provide them)
- Releasing lock will fail if any of requested resources is missing
- Extending lock will fail if any of requested resources is missing
Example:
```js
redlock.lock(['locks:account:322456', 'locks:account:322457', 'locks:account:322458'], 1000).then(function(lock) {
// ...do something here...
// if you need more time, you can continue to extend
// the lock as long as you never let it expire
// this will extend the lock so that it expires
// approximitely 1s from when `extend` is called
return lock.extend(1000).then(function(lock){
// ...do something here...
// unlock your resource when you are done
return lock.unlock()
.catch(function(err) {
// we weren't able to reach redis; your lock will eventually
// expire, but you probably want to log this error
console.error(err);
});
});
});
```
API Docs
--------
### `Redlock.prototype.lock(resource, ttl, ?callback) => Promise<Lock>`
- `resource (string or string[])` resource(s) to be locked
- `ttl (number)` time in ms until the lock expires
- `callback (function)` callback returning:
- `err (Error)`
- `lock (Lock)`
### `Redlock.prototype.unlock(lock, ?callback) => Promise`
- `lock (Lock)` lock to be released
- `callback (function)` callback returning:
- `err (Error)`
### `Redlock.prototype.extend(lock, ttl, ?callback) => Promise<Lock>`
- `lock (Lock)` lock to be extended
- `ttl (number)` time in ms to extend the lock's expiration
- `callback (function)` callback returning:
- `err (Error)`
- `lock (Lock)`
### `Redlock.prototype.disposer(resource, ttl, ?unlockErrorHandler)`
- `resource (string or string[])` resource(s) to be locked
- `ttl (number)` time in ms to extend the lock's expiration
- `callback (function)` error handler called with:
- `err (Error)`
### `Redlock.prototype.quit(?callback) => Promise<*[]>`
- `callback (function)` error handler called with:
- `err (Error)`
- `*[]` results of calling `.quit()` on each client
### `Lock.prototype.unlock(?callback) => Promise`
- `callback (function)` callback returning:
- `err (Error)`
### `Lock.prototype.extend(ttl, ?callback) => Promise<Lock>`
- `ttl (number)` time from now in ms to set as the lock's new expiration
- `callback (function)` callback returning:
- `err (Error)`
- `lock (Lock)`
Please view the (very concise) source code or TypeScript definitions for a detailed breakdown of the API.
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc