npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

redis-rwlock

v1.0.0

Published

Distributed read/write locks with Redis - queue-based, no polling, immediate acquisition

Readme

redis-rwlock

npm version CI codecov

A production-ready distributed read/write lock library for Node.js using Redis. Features queue-based waiting with immediate lock acquisition on release (no polling or retrying).

Features

  • 🔐 Read/Write Locks - Multiple readers OR single writer
  • 📋 Queue-Based Waiting - FIFO ordering, no polling needed
  • Immediate Acquisition - Waiters notified instantly when lock is released
  • ⏱️ Per-Acquisition Timeouts - Customize lock duration and wait timeout per call
  • 🔄 Lock Extension - Extend lock duration while holding it
  • ✍️ Writer Priority - Prevent writer starvation (configurable)
  • 🛡️ Auto-Release - Locks expire automatically to prevent deadlocks
  • 🔌 Connection Recovery - Handles Redis reconnection gracefully
  • 📦 TypeScript - Full type definitions included

Installation

npm install redis-rwlock ioredis

Quick Start

import Redis from 'ioredis';
import { createLockManager } from 'redis-rwlock';

const redis = new Redis();
const lockManager = createLockManager(redis);

// Acquire a write lock
const lock = await lockManager.acquire('my-resource', 'write');

try {
  // Critical section - you have exclusive access
  await updateDatabase();
} finally {
  await lock.release();
}

API

Creating a Lock Manager

import { createLockManager } from 'redis-rwlock';

const lockManager = createLockManager(redis, {
  keyPrefix: 'rwlock',           // Redis key prefix (default: 'rwlock')
  defaultLockDuration: 30000,    // Default lock TTL in ms (default: 30000)
  defaultWaitTimeout: 10000,     // Default max wait time in ms (default: 10000)
  writerPriority: true,          // Block new readers when writer waiting (default: true)
  cleanupInterval: 30000,        // Background cleanup interval (default: 30000)
  debug: false,                  // Enable debug logging (default: false)
});

Acquiring Locks

// Write lock (exclusive)
const writeLock = await lockManager.acquire('resource', 'write');

// Read lock (shared)
const readLock = await lockManager.acquire('resource', 'read');

// With custom options
const lock = await lockManager.acquire('resource', 'write', {
  lockDuration: 60000,  // Hold for 60 seconds
  waitTimeout: 5000,    // Wait max 5 seconds
  onQueued: (position) => console.log(`Queued at position ${position}`),
});

Try Without Waiting

const lock = await lockManager.tryAcquire('resource', 'write');

if (lock) {
  // Got the lock
  await lock.release();
} else {
  // Lock not available
}

Lock Operations

// Check if still valid
if (lock.isValid()) {
  // Still holding the lock
}

// Get remaining time
const remainingMs = lock.remainingTime();

// Extend the lock
const newExpiry = await lock.extend(30000); // Add 30 seconds

// Release the lock
await lock.release();

Lock Information

// Check if locked
const isLocked = await lockManager.isLocked('resource');

// Get detailed info
const info = await lockManager.getLockInfo('resource');
// Returns: { type, holders, expiresAt, queueLength, hasWriterWaiting }

// Get queue length
const queueLength = await lockManager.getQueueLength('resource');

Shutdown

// Releases all locks and cleans up
await lockManager.shutdown();

Error Handling

import { 
  AcquisitionTimeoutError, 
  NotHeldError, 
  LockExpiredError 
} from 'redis-rwlock';

try {
  const lock = await lockManager.acquire('resource', 'write', {
    waitTimeout: 1000,
  });
} catch (error) {
  if (error instanceof AcquisitionTimeoutError) {
    console.log(`Timed out waiting for ${error.resource}`);
  }
}

How It Works

Queue-Based Architecture

Unlike traditional lock implementations that use polling/retrying, redis-rwlock uses:

  1. Sorted Sets for the waiter queue (score = arrival time for FIFO)
  2. Pub/Sub for instant notification when locks are released
  3. Lua Scripts for atomic operations

When a lock is released:

  1. The release script atomically grants the lock to the next waiter(s)
  2. Notifications are published to wake up the waiters
  3. Waiters verify their status and return the lock handle

Read/Write Semantics

  • Read locks are shared - multiple readers can hold simultaneously
  • Write locks are exclusive - only one writer, no readers
  • When a write lock is released, consecutive read locks at the queue head are granted together

Writer Priority

With writerPriority: true (default):

  • Once a writer is waiting, new readers must queue behind it
  • Prevents writer starvation in read-heavy workloads

Best Practices

Always Use try/finally

const lock = await lockManager.acquire('resource', 'write');
try {
  await doWork();
} finally {
  await lock.release();
}

Set Appropriate Timeouts

// Short operations: shorter lock duration
const lock = await lockManager.acquire('resource', 'write', {
  lockDuration: 5000,   // 5 seconds max
  waitTimeout: 2000,    // Don't wait too long
});

// Long operations: longer duration, extend if needed
const lock = await lockManager.acquire('resource', 'write', {
  lockDuration: 60000,
});

// Extend before expiring
if (lock.remainingTime() < 10000) {
  await lock.extend(30000);
}

Handle Timeouts Gracefully

try {
  const lock = await lockManager.acquire('resource', 'write', {
    waitTimeout: 1000,
  });
  // ... use lock
} catch (error) {
  if (error instanceof AcquisitionTimeoutError) {
    // Return 503 or retry later
    return res.status(503).json({ error: 'Resource busy' });
  }
  throw error;
}

Contributing

Contributions are welcome! Please read our Contributing Guide for details.

License

MIT © [Your Name]