roadrunner-php/lock
PHP locking primitives for RoadRunner apps: simple, lightweight mutex/lock abstractions to coordinate concurrent jobs and prevent race conditions across workers. Useful for guarding critical sections and safely sharing resources in high-throughput services.
Install via Composer: composer require roadrunner-php/lock
Ensure RoadRunner is running with the lock plugin enabled — add lock: to your .rr.yaml config:
rpc:
enable: true
listen: tcp://127.0.0.1:6001
services:
# ... other services
lock:
# Optional: configure backend (default: "memory")
driver: "redis"
config:
host: "127.0.0.1"
port: 6379
💡 The default driver is
memory(per-worker isolation). For true distributed locking across workers, useredisormemcached.
First use case: Protect a shared resource (e.g., updating a counter) inside a long-running RoadRunner worker:
$lock = new \RoadRunner\Lock\Lock($lockService); // $lockService via DI or factory
$key = 'update_counter';
$lock->lock($key);
try {
$counter = (int) file_get_contents('counter.txt');
file_put_contents('counter.txt', $counter + 1);
} finally {
$lock->unlock($key);
}
Use with closures for atomic blocks:
$lock->wrap($key, fn() => $this->expensiveOperation());
Ensures
unlock()is always called, even on exception — ideal for avoiding deadlocks.
With a lock factory for DI:
Define a factory in your container (e.g., Laravel’s service container):
// In a service provider
$this->app->singleton(\RoadRunner\Lock\Lock::class, fn() =>
new \RoadRunner\Lock\Lock($this->app->make(\ Spiral\RoadRunner\Lock\LockServiceInterface::class))
);
Per-operation key naming: Use namespaced keys for clarity:
$key = sprintf('order:process:%d', $orderId);
Non-blocking acquisition with timeout (if using Redis/Memcached):
// Behind the scenes, rely on the underlying driver’s timeout
// RoadRunner’s Lock API supports non-blocking via tryLock() in newer versions
if ($lock->tryLock($key, $timeout = 1.5)) {
try {
// critical section
} finally {
$lock->unlock($key);
}
} else {
// fallback or retry later
}
Background job queuing: Use locks to avoid duplicate processing of high-priority jobs across workers:
if ($lock->tryLock("job:{$jobId}")) {
// Process once
}
memory driver is worker-local — locks won’t coordinate across separate workers/processes. Use Redis/Memcached for true distributed safety.lock() inside another locked section without ensuring correct ordering. Prefer wrap() for exception-safe scoping.$logger->info('Acquired lock', ['key' => $key, 'worker' => getmypid()]);
connect_timeout and timeout in config.lock.config for Redis to prevent hangs.LockServiceInterface — you can swap the backend by implementing it yourself (e.g., for DynamoDB, custom HTTP lock server).KEYS * in monitoring — use SCAN or check via INFO/CLUSTER commands.How can I help you explore Laravel packages today?