Note
This project is a Community Project.
The project is maintained and supported by the community. Upstash may contribute but does not officially support or assume responsibility for it.
@upstash/lock
offers a distributed lock and debounce implementation using Upstash Redis.
Please use this lock implementation for efficiency purposes; for example to avoid doing an expensive work more than once or to perform a task mostly once in a best-effort manner. Do not use it to guarantee correctness of your system; such as leader-election or for the tasks requiring exactly once execution.
Upstash Redis uses async replication between replicas, and a lock can be acquired by multiple clients in case of a crash or network partition. Please read the post How to do distributed locking by Martin Kleppman to learn more about the topic.
NPM
npm install @upstash/lock
PNPM
pnpm add @upstash/lock
Bun
bun add @upstash/lock
To see a demo of the lock in action, visit https://lock-upstash.vercel.app
To create the Redis instance, you can use the Redis.fromEnv()
method to use an Upstash Redis instance from environment variables. More options can be found here.
import { Lock } from "@upstash/lock";
import { Redis } from "@upstash/redis";
async function handleOperation() {
const lock = new Lock({
id: "unique-lock-id",
redis: Redis.fromEnv(),
});
if (await lock.acquire()) {
// Perform your critical section that requires mutual exclusion
await criticalSection();
await lock.release();
} else {
// handle lock acquisition failure
}
}
import { Lock } from "@upstash/lock";
import { Redis } from "@upstash/redis";
import { expensiveWork } from "my-app";
const debouncedFunction = new Debounce({
id: "unique-function-id",
redis: Redis.fromEnv(),
// Wait time of 1 second
// The debounced function will only be called once per second across all instances
wait: 1000,
// Callback function to be debounced
callback: (arg) => {
doExpensiveWork(arg);
},
});
// This example function is called by our app to trigger work we want to only happen once per wait period
async function triggerExpensiveWork(arg: string) {
// Call the debounced function
// This will only call the callback function once per wait period
await debouncedFunction.call(arg)
}
new Lock({
id: string,
redis: Redis, // ie. Redis.fromEnv(), new Redis({...})
lease: number, // default: 10000 ms
retry: {
attempts: number, // default: 3
delay: number, // default: 100 ms
},
});
Attempts to acquire the lock. Returns true
if the lock is acquired, false
otherwise.
You can pass a config
object to override the default lease
and retry
options.
async acquire(config?: LockAcquireConfig): Promise<boolean>
Attempts to release the lock. Returns true
if the lock is released, false
otherwise.
async release(): Promise<boolean>
Attempts to extend the lock lease. Returns true
if the lock lease is extended, false
otherwise.
async extend(amt: number): Promise<boolean>
Returns whether the lock is ACQUIRED
or FREE
.
async getStatus(): Promise<LockStatus>
Option | Default Value | Description |
---|---|---|
lease |
10000 |
The lease duration in milliseconds. After this expires, the lock will be released |
retry.attempts |
3 |
The number of attempts to acquire the lock. |
retry.delay |
100 |
The delay between attempts in milliseconds. |
Creates a new debounced function.
new Debounce({
id: string,
redis: Redis, // ie. Redis.fromEnv(), new Redis({...})
wait: number, // default: 1000 ms
callback: (...arg: any[]) => any // The function to be debounced
});
Calls the debounced function. The function will only be called once per wait
period.
When called there is a best-effort guarantee that the function will be called once per wait
period.
Note: Due to the implementation of the debounce, there is always a delay of wait
milliseconds before the function is called (even if the callback is not triggered when you use the call function).
async call(...args: any[]): Promise<void>