# Understanding and Using race-lock-js: A Guide to Preventing Race Conditions

In the world of asynchronous programming with Node.js, managing concurrent operations can be a significant challenge. Race conditions, where the outcome of a program depends on the unpredictable sequence or timing of events, can lead to unexpected bugs and data corruption. This is where `race-lock-js` comes in – a lightweight, in-memory lock utility designed to help you safely coordinate asynchronous operations within a single Node.js process.

### What is RaceLock JS?

`race-lock-js` (version 0.0.4) is a simple yet powerful library that provides an in-memory locking mechanism to prevent race conditions. It acts as a mutex, ensuring that only one operation can access a critical section of code at a time, thereby maintaining data integrity and predictable behavior in your asynchronous workflows.

**Key Features:**

* **Simple API:** Easy-to-use `start()` and `end()` methods for acquiring and releasing locks.
    
* **Auto-release with Timeouts:** Optional timeouts ensure locks are not held indefinitely.
    
* **Ownership Checks:** Prevents unintended release of locks by different operations.
    
* **Exponential Backoff Retry:** `retryStart()` offers a robust way to acquire locks under contention.
    
* `waitForUnlock()`: Allows operations to pause and wait until a specific lock is released.
    
* **Metadata Support:** Attach contextual information to your locks.
    
* **Introspection:** Utilities like `getLockCount()`, `getAllLockedKeys()`, and `getLockInfo()` for monitoring.
    
* **Emergency Unlocking:** `forceUnlock()` and `clearAllLocks()` for critical situations (use with caution).
    

### Installation

Getting started with `race-lock-js` is straightforward. You can install it via npm:

Bash

```javascript
npm install racelock
```

### How to Use RaceLock JS: Examples and Demo Code

Let's dive into some practical examples to understand how to leverage `race-lock-js` in your applications.

First, import the library:

JavaScript

```javascript
const RaceLock = require('racelock');
const lock = new RaceLock();
```

#### Basic Locking

This demonstrates how to acquire and release a lock for a simple asynchronous task.

JavaScript

```javascript
async function performCriticalTask(taskId) {
    const lockKey = `task-${taskId}-lock`;

    // Try to acquire the lock
    if (lock.start(lockKey)) {
        try {
            console.log(`Task ${taskId}: Lock acquired for ${lockKey}. Performing critical operation...`);
            // Simulate an asynchronous operation
            await new Promise(resolve => setTimeout(resolve, 1000));
            console.log(`Task ${taskId}: Critical operation complete.`);
        } finally {
            // Always ensure the lock is released
            lock.end(lockKey);
            console.log(`Task ${taskId}: Lock released for ${lockKey}.`);
        }
    } else {
        console.log(`Task ${taskId}: Could not acquire lock for ${lockKey}. Already locked.`);
    }
}

// Simulate multiple tasks trying to access the same resource
performCriticalTask(1);
performCriticalTask(1); // This will likely fail to acquire the lock initially
performCriticalTask(2); // This will acquire a different lock
```

#### Asynchronous Task Protection with Timeouts

You can set a timeout for a lock to automatically release if it's held for too long.

JavaScript

```javascript
async function sensitiveOperation(userId) {
    const lockKey = `user-${userId}-data`;
    const timeoutMs = 2000; // Lock will auto-release after 2 seconds

    if (lock.start(lockKey, timeoutMs, { userId: userId, operation: 'updateProfile' })) {
        try {
            console.log(`User ${userId}: Lock acquired with timeout. Updating profile...`);
            await new Promise(resolve => setTimeout(resolve, 1500)); // Simulate a task that finishes within timeout
            console.log(`User ${userId}: Profile updated successfully.`);
        } finally {
            lock.end(lockKey);
            console.log(`User ${userId}: Lock released.`);
        }
    } else {
        console.log(`User ${userId}: Failed to acquire lock for profile update.`);
    }
}

sensitiveOperation(123);
// If called again immediately, it might fail or wait for the timeout/release
sensitiveOperation(123);
```

#### Using `retryStart()` for Contention

`retryStart()` is excellent for scenarios where multiple operations might contend for the same lock. It retries acquiring the lock with an exponential backoff.

JavaScript

```javascript
async function processOrder(orderId) {
    const lockKey = `order-${orderId}-processing`;
    const ownerId = `processor-${Math.random().toFixed(4)}`; // Unique ID for this attempt

    try {
        const acquired = await lock.retryStart(
            lockKey,
            5, // Number of attempts
            100, // Initial delay (ms)
            3000, // Timeout for each lock acquisition attempt (ms)
            { orderId: orderId, source: 'web' }, // Metadata
            ownerId
        );

        if (acquired) {
            console.log(`${ownerId} for Order ${orderId}: Lock acquired. Processing order...`);
            await new Promise(resolve => setTimeout(resolve, 2500)); // Simulate order processing
            console.log(`${ownerId} for Order ${orderId}: Order processed.`);
        } else {
            console.error(`${ownerId} for Order ${orderId}: Failed to acquire lock after multiple attempts.`);
        }
    } catch (error) {
        console.error(`${ownerId} for Order ${orderId}: Error during lock acquisition or processing:`, error.message);
    } finally {
        // Ensure lock is released by its owner
        if (lock.isLocked(lockKey) && lock.getLockInfo(lockKey)?.ownerId === ownerId) {
            lock.end(lockKey, ownerId);
            console.log(`${ownerId} for Order ${orderId}: Lock released.`);
        }
    }
}

// Simulate multiple attempts to process the same order
processOrder(101);
processOrder(101);
processOrder(102);
```

#### Waiting for a Lock to be Released with `waitForUnlock()`

If an operation needs to wait until a specific lock is free, `waitForUnlock()` is the perfect solution.

JavaScript

```javascript
async function consumerProcess(dataKey) {
    console.log(`Consumer for ${dataKey}: Checking if lock is active...`);
    while (lock.isLocked(dataKey)) {
        console.log(`Consumer for ${dataKey}: Lock is active, waiting...`);
        await lock.waitForUnlock(dataKey);
    }
    console.log(`Consumer for ${dataKey}: Lock is no longer active. Proceeding with data processing.`);
    // Now you can safely access or modify the data
    await new Promise(resolve => setTimeout(resolve, 500));
    console.log(`Consumer for ${dataKey}: Data processing complete.`);
}

async function producerProcess(dataKey) {
    const ownerId = 'producer-A';
    if (lock.start(dataKey, 0, {}, ownerId)) {
        try {
            console.log(`Producer for ${dataKey}: Lock acquired. Producing data...`);
            await new Promise(resolve => setTimeout(resolve, 3000)); // Simulate data production
            console.log(`Producer for ${dataKey}: Data production complete.`);
        } finally {
            lock.end(dataKey, ownerId);
            console.log(`Producer for ${dataKey}: Lock released.`);
        }
    }
}

const sharedDataKey = 'mySharedResource';

// Start consumer first, it will wait
consumerProcess(sharedDataKey);
// Start producer after a short delay, it will acquire the lock
setTimeout(() => producerProcess(sharedDataKey), 500);
```

### Best Practices for Using `race-lock-js`

* **Always Use** `finally` Blocks: Ensure `lock.end()` is called within a `finally` block to guarantee lock release, even if errors occur.
    
* **Utilize** `ownerId`: When multiple systems or functions might interact with the same lock key, use `ownerId` to enforce ownership and prevent accidental releases.
    
* **Employ** `retryStart()` for Contention: If you anticipate contention for a lock, `retryStart()` provides a robust mechanism to acquire it gracefully.
    
* **Use** `clearAllLocks()` with Extreme Caution: This function clears all active locks and should only be used in emergency scenarios or for testing, as it can disrupt ongoing operations.
    

### License and Contributions

`race-lock-js` is open-source, licensed under the MIT License. Contributions are welcome! You can contribute by forking the repository, starring it, submitting pull requests, or opening issues on its [GitHub repository](https://www.npmjs.com/package/race-lock-js).

For more details, you can visit the [npm package page](https://www.npmjs.com/package/race-lock-js) directly.
