Skip to main content
Version: Next

Mutex

A mutex is like a "do not disturb" sign for your data. It ensures that only one operation can access a shared resource at a time. Imagine multiple people trying to edit the same document - a mutex makes sure only one person can edit it at once.

Basic Usage

The easiest way to use a mutex is with the withMutex function, which automatically handles locking and unlocking:

import { withMutex } from 'commandkit/mutex';

// This ensures only one operation can update the shared resource at a time
const result = await withMutex('shared-resource', async () => {
// This code runs with exclusive access
return await updateSharedResource();
});

Custom Configuration

You can create a custom mutex with different timeout settings:

import { createMutex } from 'commandkit/mutex';

const mutex = createMutex({
timeout: 60000, // 60 second timeout
});

const result = await mutex.withLock('resource', async () => {
return await criticalOperation();
});

Advanced Usage

Manual Lock Management

Sometimes you need more control over when locks are acquired and released:

import { acquireLock, releaseLock, isLocked } from 'commandkit/mutex';

// Acquire lock manually
const acquired = await acquireLock('resource', 30000);
if (acquired) {
try {
// Perform critical operation
await criticalOperation();
} finally {
// Always release the lock, even if an error occurs
await releaseLock('resource');
}
}

// Check if resource is currently locked
const locked = await isLocked('resource');
console.log(`Resource is ${locked ? 'locked' : 'available'}`);

Cancelling Operations

You can cancel a mutex operation if it takes too long or if you need to stop it for any reason:

import { withMutex } from 'commandkit/mutex';

// Create a timeout that cancels after 5 seconds
const signal = AbortSignal.timeout(5000);

try {
const result = await withMutex(
'resource',
async () => {
return await longRunningOperation();
},
30000,
signal,
);
} catch (error) {
if (error.message.includes('aborted')) {
console.log('Lock acquisition was cancelled');
}
}

Using External Storage

By default, mutexes store lock information in memory. If you're running multiple servers, you'll want to use external storage like Redis:

import { Mutex, MutexStorage } from 'commandkit/mutex';
import { RedisMutexStorage } from '@commandkit/redis';
import { Redis } from 'ioredis';

// Create Redis client
const redis = new Redis();

// Use Redis-based mutex storage
const mutex = new Mutex({
timeout: 30000,
storage: new RedisMutexStorage(redis),
});

You can also use the convenience function:

import { createMutex } from 'commandkit/mutex';
import { RedisMutexStorage } from '@commandkit/redis';

const mutex = createMutex({
timeout: 60000,
storage: new RedisMutexStorage(redis),
});

Default Settings

  • Timeout: 30 seconds (30000ms)
  • Storage: In-memory (works for single-server applications)

Common Use Cases

  • Database Transactions: Ensure only one operation can modify data at a time
  • File System Access: Prevent multiple operations from writing to the same file
  • Configuration Updates: Make sure configuration changes don't conflict
  • Cache Invalidation: Control when cache is cleared to prevent race conditions
  • Resource Pool Management: Manage access to limited resources

Tips for Beginners

  1. Use withMutex When Possible: It automatically handles cleanup, so you don't forget to release locks
  2. Set Reasonable Timeouts: Don't make timeouts too short (might fail unnecessarily) or too long (might hang forever)
  3. Use Descriptive Names: Give your resources meaningful names like user:123:profile or database:users
  4. Handle Errors: Always handle cases where lock acquisition fails
  5. Think About Deadlocks: Be careful not to create situations where two operations wait for each other
  6. Consider Your Setup: Use external storage if you have multiple servers