mimatus / locksmith
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 1
Open Issues: 2
pkg:composer/mimatus/locksmith
Requires
- php: >=8.3
Requires (Dev)
- ext-redis: *
- phpunit/phpunit: ^12.5
Suggests
- ext-redis: To use this library with the PHP Redis extension.
This package is not auto-updated.
Last update: 2026-02-23 14:43:40 UTC
README
Locksmith
PHP 8.3+ library which helps to manage execution in concurent situations (async code, parallel, distributed systems).
Installation
composer require mimatus/locksmith
Features
- Mutual exclusion / exclusive locks / mutex / serialized execution / semaphore with capacity 1 - lot of buzzwords describing ensurance resource/code is accessed/executed only once
- Semaphore / shared lock - limit access to resource N times at the same time
- Deadlock prevention via max lock wait time and cooperative suspension points
- Versioned locks - resource is locked only for higher/equal versions - prevents processing of outdated data/code
- Async/concurrent-friendly - cooperative suspension points to allow other lock acquisition attempts or allow lock TTL checks for long running processes
- TTL-based locks - locks are held only for specified time
- In-memory semaphore implementation for single-process scenarios
- Redis-based semaphore implementation for multi-process/distributed scenarios
- Extendable via
Semaphoreinterface - implement your own semaphore (e.g., Redis-based, database-based, etc.) - Deadlock prevention via max lock wait time and cooperative suspension points
Roadmap
- Basic in-memory & Redis semaphore implementation
- Redlock algorithm for Redis semaphore
- Predis support for Redis semaphore
- AMPHP Redis client support for Redis semaphore
- First class support and tests for Redis 7 | Redis 8 | Valkey 9
- Feedback and API stabilization
- Redis Cluster support
- Documentation improvements
- MySQL/MariaDB/PostgreSQL semaphore implementation
Usage
Note
Project is still in early stages of development, so API is not stable yet and may change. Feedback is very welcome to help shape the API and make it more intuitive and easy to use.
In-Memory semaphore
For single-process scenarios you can use in-memory semaphore implementation. It allows to limit concurrent access to resource within single process (e.g., number of concurrent HTTP requests, background jobs, or other tasks).
It's suitable mainly for concurrent PHP - AMPHP, Swoole, ReactPHP, etc.
It's not suitable for multi-process scenarios (e.g., multiple PHP-FPM workers, multiple servers) as each process/server will have its own instance of in-memory semaphore. For multi-process scenarios you should use Redis-based semaphore implementation.
$locksmith = new Locksmith( semaphore: new InMemorySemaphore(maxConcurrentLocks: 1) // Single lock at a time -> mutex ); $resource = new Resource( namespace: 'test-resource', // Namespace/identifier for resource version: 1, // Optional resource version ); $locked = $locksmith->locked( $resource, lockTTLNs: 1_000_000_000, // How long should be resource locked maxLockWaitNs: 500_000_000, // How long to wait for lock acquisition - error if exceeded minSuspensionDelayNs: 10_000 // Minimum delay between retries when lock acquisition fails ); $locked(function (Closure $suspension): void { // Critical section - code executed under lock $suspension(); // Optional - cooperative suspension point to allow other lock acquisition attempts or allow lock TTL checks for long running processes }); // Lock is released after callback execution
Redis semaphore
For distributed scenarios you can use Redis-based semaphore implementation.
Supported Redis servers:
- Redis 7+
- Valkey 9+
Supported Redis clients:
- PhpRedis
- Predis
- AMPHP Redis client
$redis = new Redis(); $redis->connect('redis'); $phpRedisCleint = new PhpRedisClient($redis); $semaphore = new RedisSemaphore( redisClient: $phpRedisCleint, maxConcurrentLocks: 3, // Max concurrent locks ); $locksmith = new Locksmith(semaphore: $semaphore); $resource = new Resource( namespace: 'test-resource', // Namespace/identifier for resource version: 1, // Optional resouce version ); $locked = $locksmith->locked( $resource, lockTTLNs: 1_000_000_000, // How long should be resource locked maxLockWaitNs: 500_000_000, // How long to wait for lock acquisition - error if exceeded minSuspensionDelayNs: 10_000 // Minimum delay between retries when lock acquisition fails ); $locked(function (Closure $suspension): void { // Critical section - code executed under lock $suspension(); // Optional - cooperative suspension point to allow other lock acquisition attempts or allow lock TTL checks for long running processes }); // Lock is released after callback execution
Distributed semaphore
Distributed semaphore allows to use multiple semaphore instances (e.g., multiple Redis instances) to achieve higher availability and fault tolerance. It uses quorum-based approach - single lock is successful only if the defined quorum of semaphores is reached.
Implementation of distributed semaphore is based on Redlock algorithm with some adjustments to fit the Semaphore interface and allow cooperative suspension points.
Note
It's important to note that while distributed semaphore can be used Redis instances, it does not have first class support for Redis Cluster or Sentinel. First class support for Redis Cluster is on the roadmap, but in the meantime you can use distributed semaphore with multiple independent Redis instances as a workaround.
$semaphores = new SemaphoreCollection([ new RedisSemaphore( redisClient: $redisClient1, maxConcurrentLocks: 3, ), new RedisSemaphore( redisClient: $redisClient2, maxConcurrentLocks: 3, ), new RedisSemaphore( redisClient: $redisClient3, maxConcurrentLocks: 3, ), ]); $locksmith = new Locksmith( semaphore: new DistributedSemaphore( semaphores: $semaphores, quorum: 2, ), ); $resource = new Resource( namespace: 'test-resource', // Namespace/identifier for resource version: 1, // Optional resource version ); $locked = $locksmith->locked( $resource, lockTTLNs: 1_000_000_000, // How long should be resource locked maxLockWaitNs: 500_000_000, // How long to wait for lock acquisition - error if exceeded minSuspensionDelayNs: 10_000 // Minimum delay between retries when lock acquisition fails ); $locked(function (Closure $suspension): void { // Critical section - code executed under lock $suspension(); // Optional - cooperative suspension point to allow other lock acquisition attempts or allow lock TTL checks for long running processes }); // Lock is released after callback execution
Development
Commits
Project follows Conventional Commits for commit messages.
Helpers
All necessary commands are available via Makefile.
To see all available commands:
make help
The most important ones:
composer-install: Install PHP dependencies via Composer. help: Show help for each of the Makefile recipes. mago-analyze: Run static analysis via mago. mago-format: Run code formatting via mago. mago-lint-fix: Run linting with auto-fix via mago. mago-lint: Run linting via mago. run-tests: Run tests via PHPUnit.
License
MIT — see License.