Distributed Locking
Implement distributed locking in BoxLang applications using Redis to prevent concurrent access issues.
🔒 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
<bx:RedisLock
name="lockName"
cache="redisCacheName"
timeout="2"
expires="60"
throwOnTimeout="true"
bypass="false"
>
<!-- Protected code here -->
</bx:RedisLock>Script Syntax:
bx:redisLock name="lockName" cache="redisCacheName" timeout=2 expires=60 throwOnTimeout=true bypass=false {
// Protected code here
}⚙️ Attributes
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
// 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
// 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 notDevelopment Bypass
// Bypass locking in development
var isDevelopment = application.environment == "development";
redisLock
name="syncInventory"
cache="redisCache"
bypass=isDevelopment
{
inventoryService.synchronizeWithWarehouse();
}XML Component Syntax
<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
// 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
// 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
// Ensure database migrations run only once across cluster
redisLock name="dbMigration" cache="redisCache" timeout=30 expires=600 {
migrationService.runPendingMigrations();
}Sequential Processing
// 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:
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:
// 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
// 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
timeoutbased on expected wait timeSet
expireslonger than maximum expected execution timeUse
throwOnTimeout=falsefor non-critical operations
// 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/catchinside lock body
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
bypassattribute for development environmentsSet via environment variables or application settings
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
timeoutvalueCheck 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
expiresis set appropriatelyVerify 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:
# List all lock keys
redis-cli KEYS "*lock*"
# Check lock TTL
redis-cli TTL "lock:lockName"
# View lock details
redis-cli GET "lock:lockName"Last updated
Was this helpful?
