Cookbook
Lock Integration - Usage Guide
Distributed locking with Symfony Lock for safe concurrent processing
Overview
Glueful now includes Symfony Lock component integration for distributed locking. This prevents race conditions in queue workers, scheduled tasks, and other concurrent processes.
Quick Start
Basic Lock Usage
use Glueful\Lock\LockManagerInterface;
// Get lock manager from container
$lockManager = container()->get(LockManagerInterface::class);
// Simple lock
$lock = $lockManager->createLock('my-resource');
if ($lock->acquire()) {
try {
// Critical section - only one process can execute this
processImportantTask();
} finally {
$lock->release();
}
}
Execute with Lock (Recommended)
// Automatic lock management with exception safety
$result = $lockManager->executeWithLock('import-users', function() {
return importUsersFromCSV();
}, 300); // 5 minute TTL
Wait and Execute
// Wait up to 10 seconds for lock, then execute
$result = $lockManager->waitAndExecute('process-payments', function() {
return processPayments();
}, 10.0, 300); // maxWait: 10s, TTL: 5min
Configuration
Lock Stores
Configure lock storage in config/lock.php
:
return [
'default' => env('LOCK_DRIVER', 'file'),
'stores' => [
'redis' => [
'driver' => 'redis',
'connection' => 'default',
'prefix' => 'glueful_lock_',
],
'database' => [
'driver' => 'database',
'table' => 'locks',
],
'file' => [
'driver' => 'file',
'path' => 'framework/locks',
],
],
];
Environment Variables
# Lock configuration
LOCK_DRIVER=redis
LOCK_PREFIX=myapp_lock_
LOCK_TTL=300
Common Use Cases
1. Queue Worker Coordination
Automatically integrated in WorkCommand
:
// In WorkCommand - prevents duplicate workers
$this->lockManager->executeWithLock("queue:manager:{$queue}:" . gethostname(), function() {
$this->processManager->scale($workerCount, $queue, $workerOptions);
}, 60);
2. Scheduled Task Locks
Automatically integrated in JobScheduler
:
// In JobScheduler - prevents overlapping executions
$lockResource = "scheduler:job:{$jobName}";
$this->lockManager->executeWithLock($lockResource, function() use ($job) {
return $this->executeJob($job);
}, 3600); // 1 hour max execution
3. Data Processing Locks
// Prevent concurrent data imports
$lockManager->executeWithLock('import:customers', function() {
$this->importCustomerData();
$this->updateSearchIndex();
$this->clearCache();
});
4. Cache Warming Locks
// Prevent multiple cache warming processes
$lockManager->executeWithLock('cache:warm:products', function() {
$this->warmProductCache();
}, 1800); // 30 minutes
5. File Processing Locks
// Process files one at a time
foreach ($files as $file) {
$lockKey = 'file:process:' . basename($file);
try {
$lockManager->executeWithLock($lockKey, function() use ($file) {
return $this->processFile($file);
}, 3600);
echo "Processed: {$file}\n";
} catch (LockConflictedException $e) {
echo "Skipping {$file} - already processing\n";
}
}
Advanced Usage
Manual Lock Management
$lock = $lockManager->createLock('my-resource', 300, true); // TTL, auto-release
if ($lock->acquire()) {
try {
// Check remaining time
$remaining = $lock->getRemainingLifetime();
echo "Lock expires in {$remaining} seconds\n";
// Refresh lock if needed
if ($remaining < 60) {
$lock->refresh(300); // Extend by 5 minutes
}
// Do work
performLongRunningTask();
} finally {
$lock->release();
}
} else {
echo "Could not acquire lock\n";
}
Lock Status Checking
// Check if resource is locked (non-blocking)
if ($lockManager->isLocked('my-resource')) {
echo "Resource is currently locked\n";
} else {
// Safe to proceed
}
Blocking Lock Acquisition
$lock = $lockManager->createLock('resource');
// Block until lock is acquired
if ($lock->acquire(true)) { // blocking = true
try {
// Work here
} finally {
$lock->release();
}
}
Lock Naming Conventions
Use descriptive, hierarchical lock names:
// Good examples
"queue:worker:{$queue}:{$workerId}"
"scheduler:job:{$jobName}"
"import:users:batch:{$batchId}"
"cache:warm:{$cacheKey}"
"process:payments:daily"
// Avoid generic names
"lock"
"process"
"task"
Error Handling
Handle Lock Conflicts
use Symfony\Component\Lock\Exception\LockConflictedException;
try {
$lockManager->executeWithLock('my-resource', function() {
// Critical section
});
} catch (LockConflictedException $e) {
// Lock could not be acquired
$this->logger->info('Resource busy, skipping execution');
} catch (\Exception $e) {
// Other errors
$this->logger->error('Execution failed: ' . $e->getMessage());
}
Timeout Handling
try {
$result = $lockManager->waitAndExecute('resource', function() {
return doWork();
}, 30.0); // 30 second timeout
} catch (LockConflictedException $e) {
echo "Timeout: Could not acquire lock within 30 seconds\n";
}
Performance Tips
1. Use Appropriate TTLs
// Short tasks
$lockManager->executeWithLock('quick-task', $callback, 60);
// Long-running imports
$lockManager->executeWithLock('data-import', $callback, 3600);
// Background jobs
$lockManager->executeWithLock('background-job', $callback, 7200);
2. Choose the Right Store
- File: Development, single-server setups
- Redis: Multi-server, high-performance (recommended)
- Database: When Redis isn't available
3. Use Descriptive Lock Names
// Include relevant identifiers
$lockKey = "user:export:{$userId}:format:{$format}";
$lockKey = "report:generate:{$reportType}:date:{$date}";
4. Monitor Lock Usage
// Log lock acquisition for monitoring
$lockManager->executeWithLock($resource, function() use ($resource) {
$this->logger->info("Acquired lock: {$resource}");
return $this->doWork();
});
CLI Commands
Queue Worker with Locks
# Workers automatically coordinate using locks
php glueful queue:work --workers=3 --queue=default
# Scale workers safely
php glueful queue:work scale --count=5 --queue=processing
Scheduler with Locks
# Run scheduled jobs (prevents overlaps)
php glueful queue:scheduler run
# Run in worker mode
php glueful queue:scheduler work --interval=60
Troubleshooting
1. Locks Not Working
Check your configuration:
# Verify lock configuration
php glueful config:show lock
# Test lock store connectivity
php glueful system:check
2. Stuck Locks
Locks automatically expire based on TTL, but you can check:
// Check lock expiration
$lock = $lockManager->createLock('stuck-resource');
if ($lock->isExpired()) {
echo "Lock has expired\n";
}
3. Performance Issues
- Use Redis for better performance
- Optimize lock TTLs
- Avoid creating too many locks
4. Database Locks Table
Ensure the locks table exists:
php glueful migrate run
Best Practices
- Always use try/finally or executeWithLock()
- Set appropriate TTLs for your use case
- Use descriptive lock names with context
- Handle LockConflictedException gracefully
- Monitor lock usage in production
- Use Redis store for multi-server setups
- Don't hold locks longer than necessary
- Log lock operations for debugging
Integration Examples
Custom Service with Locks
use Glueful\Lock\LockManagerInterface;
class DataSyncService
{
public function __construct(
private LockManagerInterface $lockManager
) {}
public function syncUsers(): void
{
$this->lockManager->executeWithLock('sync:users', function() {
$this->fetchUsersFromAPI();
$this->updateDatabase();
$this->invalidateCache();
}, 1800); // 30 minutes
}
public function syncInBatches(array $userIds): void
{
foreach (array_chunk($userIds, 100) as $batch) {
$batchId = md5(implode(',', $batch));
$this->lockManager->executeWithLock("sync:batch:{$batchId}", function() use ($batch) {
$this->syncUserBatch($batch);
}, 300);
}
}
}
This integration provides robust distributed locking for all your critical sections!