# Distributed Locking

## 🔒 Overview

In clustered environments, it is often necessary to prevent multiple instances from executing methods at the same time, preventing collisions with data access and modification. The `bx:RedisLock` component provides distributed locking across all running instances in a cluster using Redis as the coordination mechanism.

The component functions similarly to the standard `lock` component but uses Redis to coordinate locks across multiple servers, ensuring that only one request can execute the locked code at any given time across the entire cluster.

## 📋 Component Syntax

```xml
<bx:RedisLock
    name="lockName"
    cache="redisCacheName"
    timeout="2"
    expires="60"
    throwOnTimeout="true"
    bypass="false"
>
    <!-- Protected code here -->
</bx:RedisLock>
```

**Script Syntax:**

```js
bx:redisLock name="lockName" cache="redisCacheName" timeout=2 expires=60 throwOnTimeout=true bypass=false {
    // Protected code here
}
```

## ⚙️ Attributes

| Attribute        | Type    | Required | Default | Description                                                                                                                                      |
| ---------------- | ------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `name`           | string  | ✅ Yes    | -       | Lock name. Only one request can execute code within a lock with a given name at a time. Cannot be an empty string.                               |
| `cache`          | string  | ✅ Yes    | -       | The name of the Redis cache (as defined in application cache settings) to use for the lock                                                       |
| `timeout`        | integer | No       | `2`     | Maximum time in seconds to wait to obtain a lock. Must be >= 0. If lock is not obtained within this time, behavior depends on `throwOnTimeout`.  |
| `expires`        | integer | No       | `60`    | The length of time in seconds before the lock automatically expires. Must be >= 1. Prevents deadlocks if a process crashes while holding a lock. |
| `throwOnTimeout` | boolean | No       | `true`  | If `true`, throws a runtime exception if lock is not obtained within timeout period. If `false`, skips the body and continues execution.         |
| `bypass`         | boolean | No       | `false` | If `true`, bypasses the lock entirely and executes the body immediately. Useful for development environments.                                    |

## 💡 Usage Examples

### Basic Distributed Lock

```js
// Protect critical section across all servers
redisLock name="processOrders" cache="redisCache" timeout=5 expires=30 {
    // Only one server can process orders at a time
    var orders = orderService.getPendingOrders();
    for( var order in orders ) {
        orderService.processOrder( order );
    }
}
```

### Lock with Timeout Handling

```js
// Non-blocking lock - skip if unavailable
redisLock
    name="updateCache"
    cache="redisCache"
    timeout=1
    expires=10
    throwOnTimeout=false
{
    cacheService.rebuildExpensiveCache();
}

// Continue execution whether lock was obtained or not
```

### Development Bypass

```js
// Bypass locking in development
var isDevelopment = application.environment == "development";

redisLock
    name="syncInventory"
    cache="redisCache"
    bypass=isDevelopment
{
    inventoryService.synchronizeWithWarehouse();
}
```

### XML Component Syntax

```xml
<bx:RedisLock
    name="dailyReport"
    cache="redisCache"
    timeout="10"
    expires="300"
>
    <bx:set var="reportData" value="#reportService.generateDailyReport()#" />
    <bx:set var="result" value="#emailService.sendReport( reportData )#" />
</bx:RedisLock>
```

## 🎯 Common Use Cases

### Scheduled Task Coordination

```js
// Ensure scheduled task runs on only one server
component {
    function runScheduledTask() {
        redisLock name="dailyCleanup" cache="redisCache" timeout=0 throwOnTimeout=false {
            // Only executes on one server
            cleanupService.performDailyMaintenance();
        }
    }
}
```

### Cache Warming

```js
// Prevent multiple servers from warming cache simultaneously
redisLock name="warmCache" cache="redisCache" timeout=2 expires=120 throwOnTimeout=false {
    if( !cacheService.isWarmed() ) {
        cacheService.warmCache();
    }
}
```

### Database Migration Coordination

```js
// Ensure database migrations run only once across cluster
redisLock name="dbMigration" cache="redisCache" timeout=30 expires=600 {
    migrationService.runPendingMigrations();
}
```

### Sequential Processing

```js
// Process queue items one at a time across all servers
redisLock name="processQueue" cache="redisCache" timeout=5 expires=60 {
    var item = queue.getNextItem();
    if( !isNull( item ) ) {
        processItem( item );
        queue.markComplete( item );
    }
}
```

## 🔧 Configuration Requirements

### Cache Setup

The lock requires a configured Redis cache in your application:

```js
component {
    this.name = "MyApp";
    this.sessionManagement = true;

    // Configure Redis cache
    this.cache.redis = {
        provider: "Redis",
        properties: {
            server: "localhost",
            port: 6379
        }
    };
}
```

### Cluster Configuration

For multi-server environments, ensure all servers point to the same Redis instance:

```js
// Production configuration
this.cache.redis = {
    provider: "Redis",
    properties: {
        server: "redis.production.com",
        port: 6379,
        password: getSystemSetting( "REDIS_PASSWORD" ),
        ssl: true
    }
};
```

## ⚠️ Best Practices

### Lock Naming

* Use descriptive, unique lock names
* Include context in name: `"user_#{userId}_update"` instead of `"update"`
* Avoid hardcoded IDs - use dynamic names when locking specific resources

```js
// Good: Specific resource lock
redisLock name="invoice_#invoiceId#_generate" cache="redisCache" {
    generateInvoice( invoiceId );
}

// Bad: Too generic
redisLock name="generate" cache="redisCache" {
    generateInvoice( invoiceId );
}
```

### Timeout Configuration

* Set `timeout` based on expected wait time
* Set `expires` longer than maximum expected execution time
* Use `throwOnTimeout=false` for non-critical operations

```js
// Critical operation - throw if can't acquire
redisLock name="payment" cache="redisCache" timeout=10 expires=30 throwOnTimeout=true {
    processPayment();
}

// Non-critical - skip if unavailable
redisLock name="analytics" cache="redisCache" timeout=1 expires=60 throwOnTimeout=false {
    updateAnalytics();
}
```

### Error Handling

* Always handle exceptions within locked code
* Keep locked code sections as short as possible
* Consider using `try/catch` inside lock body

```js
redisLock name="criticalUpdate" cache="redisCache" {
    try {
        performCriticalUpdate();
    } catch( any e ) {
        logService.error( "Lock execution failed", e );
        // Lock will be released automatically
        rethrow;
    }
}
```

### Development vs Production

* Use `bypass` attribute for development environments
* Set via environment variables or application settings

```js
var isDev = application.environment == "development";

redisLock name="sync" cache="redisCache" bypass=isDev {
    syncService.synchronize();
}
```

## 🚨 Troubleshooting

### Lock Not Acquired

**Symptoms:** `throwOnTimeout` exceptions or body not executing

**Solutions:**

* Increase `timeout` value
* Check if another process is holding the lock too long
* Verify Redis connectivity
* Check Redis logs for connection issues

### Deadlocks

**Symptoms:** Locks never release, operations hang

**Solutions:**

* Ensure `expires` is set appropriately
* Verify locked code completes normally
* Check for infinite loops in locked sections
* Monitor Redis for stuck lock keys

### Performance Issues

**Symptoms:** High latency acquiring locks

**Solutions:**

* Reduce lock scope - lock smaller code sections
* Increase Redis server resources
* Check network latency to Redis
* Consider Redis Cluster for high-availability

### Cache Not Found

**Error:** "The specified cache \[name] is not a Redis cache"

**Solutions:**

* Verify cache name matches application configuration
* Ensure cache provider type is "Redis"
* Check cache is properly initialized before use

## 🔍 Monitoring

### Lock Metrics

Monitor these aspects of distributed locking:

* **Lock acquisition time** - time spent waiting for locks
* **Lock hold duration** - time locks are held
* **Failed acquisitions** - locks that timeout
* **Lock expiration events** - automatic releases

### Redis Key Inspection

Locks are stored in Redis with specific key patterns. Use Redis CLI to inspect:

```bash
# List all lock keys
redis-cli KEYS "*lock*"

# Check lock TTL
redis-cli TTL "lock:lockName"

# View lock details
redis-cli GET "lock:lockName"
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://boxlang.ortusbooks.com/boxlang-+-++/modules/bx-redis/distributed-locking.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
