| name | symfony-semaphore |
| description | Manage semaphores to allow multiple concurrent processes to access shared resources with configurable limits. Use semaphores for rate limiting, resource pooling, and coordinating concurrent access across multiple processes on local or remote systems. |
Symfony Semaphore Component
Overview
The Semaphore Component provides a mechanism to manage semaphores — synchronization primitives that allow multiple processes to access a shared resource concurrently up to a specified limit. Unlike locks which restrict access to a single process, semaphores enable controlled concurrent access.
Key Difference:
- Semaphore: Multiple processes can access a resource (up to limit)
- Lock: Only one process can access a resource
Installation
Install the Semaphore component via Composer:
composer require symfony/semaphore
For standalone use outside Symfony applications, ensure Composer autoloading is included:
require_once 'vendor/autoload.php';
Core Classes and Interfaces
SemaphoreFactory
The primary factory for creating semaphore instances. Accepts a storage backend and creates semaphore objects.
Key Method:
createSemaphore(string $resource, int $limit, ?bool $blocking = true, ?float $timeout = 300.0, ?bool $autoRelease = true): SemaphoreInterface
$resource (string): Arbitrary identifier for the resource being semaphored
$limit (int): Maximum number of concurrent processes allowed to acquire the semaphore
$blocking (bool): Whether acquire() should block until available (default: true)
$timeout (float): Maximum time to wait for acquisition in seconds (default: 300)
$autoRelease (bool): Auto-release on instance destruction (default: true)
SemaphoreInterface
Main interface for semaphore instances.
Key Methods:
Available Stores
RedisStore
Use Redis as the backend for distributed semaphore management.
Configuration:
use Symfony\Component\Semaphore\SemaphoreFactory;
use Symfony\Component\Semaphore\Store\RedisStore;
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$store = new RedisStore($redis);
$factory = new SemaphoreFactory($store);
With Redis cluster:
$redis = new RedisCluster(null, ['127.0.0.1:7000', '127.0.0.1:7001']);
$store = new RedisStore($redis);
$factory = new SemaphoreFactory($store);
DynamoDbStore
Use AWS DynamoDB for serverless semaphore management.
Configuration:
use Symfony\Component\Semaphore\SemaphoreFactory;
use Symfony\Component\Semaphore\Store\DynamoDbStore;
use AsyncAws\DynamoDb\DynamoDbClient;
$client = new DynamoDbClient();
$store = new DynamoDbStore($client, 'semaphores-table');
$factory = new SemaphoreFactory($store);
Common Use Cases and Examples
Basic Semaphore Usage
Limit concurrent access to a resource to a maximum of N processes:
use Symfony\Component\Semaphore\SemaphoreFactory;
use Symfony\Component\Semaphore\Store\RedisStore;
$redis = new Redis();
$redis->connect('127.0.0.1');
$store = new RedisStore($redis);
$factory = new SemaphoreFactory($store);
$semaphore = $factory->createSemaphore('pdf-invoice-generation', 2);
if ($semaphore->acquire()) {
try {
generateInvoice();
} finally {
$semaphore->release();
}
}
Rate Limiting with Semaphores
Control the rate of resource-intensive operations:
$semaphore = $factory->createSemaphore('api-requests', 5);
if ($semaphore->acquire()) {
try {
callExternalApi();
} finally {
$semaphore->release();
}
}
Non-Blocking Acquisition
Try to acquire without waiting:
if ($semaphore->acquire(blocking: false)) {
try {
} finally {
$semaphore->release();
}
} else {
logWarning('Semaphore unavailable, skipping operation');
}
With Timeout
Limit the wait time for acquisition:
$semaphore = $factory->createSemaphore('resource', 3);
if ($semaphore->acquire(timeout: 5.0)) {
try {
} finally {
$semaphore->release();
}
}
Disabling Auto-Release (Persistent Locking)
For cross-request or persistent locking:
$semaphore = $factory->createSemaphore(
'resource',
limit: 2,
autoRelease: false
);
if ($semaphore->acquire()) {
$semaphore->release();
}
Shared Semaphore Instances
Share the same semaphore instance across multiple services:
class ApiService {
private $semaphore;
public function __construct(SemaphoreFactory $factory) {
$this->semaphore = $factory->createSemaphore('api-calls', 10);
}
public function execute() {
if ($this->semaphore->acquire()) {
try {
} finally {
$this->semaphore->release();
}
}
}
}
Important Considerations
Instance Distinction
Semaphore instances are distinct objects even when created for the same resource and limit. For shared coordination:
- Store the semaphore instance in a service or container
- Pass the same instance to all code that needs it
- Avoid creating multiple semaphore instances for the same resource
Automatic Release
By default, semaphores are automatically released when the instance is destroyed:
function processWithSemaphore($factory) {
$semaphore = $factory->createSemaphore('task', 2);
$semaphore->acquire();
}
Manual Release
Always explicitly release in try-finally blocks to ensure release on exceptions:
$semaphore = $factory->createSemaphore('resource', 2);
if ($semaphore->acquire()) {
try {
} finally {
$semaphore->release();
}
}
Blocking Behavior
The $blocking parameter controls behavior when the semaphore limit is reached:
$semaphore->acquire(blocking: true);
$semaphore->acquire(blocking: false);
Store Comparison
| Store | Type | Distribution | Best For |
|---|
| RedisStore | Remote | Multi-server | Distributed systems, microservices |
| DynamoDbStore | Remote | Multi-server | Serverless, AWS environments |
References
- Main Classes:
SemaphoreFactory, SemaphoreInterface
- Stores:
RedisStore, DynamoDbStore
- Related: Compare with the Lock Component for exclusive resource access
- GitHub: https://github.com/symfony/semaphore