Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Semaphore Laravel Package

symfony/semaphore

The Symfony Semaphore Component manages semaphores, a mechanism that provides exclusive access to a shared resource. It is a standalone PHP library you can use in any project to coordinate concurrent processes and prevent race conditions.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation (pin the current security release):

    composer require symfony/semaphore:^5.4.54
    

    5.4.54 is the [SECURITY] patch release — do not pin below it. The component is framework-agnostic, so there is no Laravel auto-discovered service provider; bind the interface yourself in a service provider:

    // app/Providers/AppServiceProvider.php
    public function register(): void
    {
        $this->app->singleton(\Symfony\Component\Semaphore\SemaphoreInterface::class, function () {
            return new \Symfony\Component\Semaphore\Semaphore(
                new \Symfony\Component\Semaphore\Store\RedisStore(
                    \Illuminate\Support\Facades\Redis::connection('default')->client()
                )
            );
        });
    }
    
  2. Basic Usage:

    // Acquire a semaphore (blocking)
    $semaphore = app(\Symfony\Component\Semaphore\SemaphoreInterface::class);
    $semaphore->acquire('critical_section_key');
    
    // Critical section (e.g. update shared resource)
    try {
        // Your logic here
    } finally {
        $semaphore->release(); // Always release!
    }
    
  3. Non-Blocking Check:

    if ($semaphore->acquire('key', 0)) { // 0 = non-blocking
        // Proceed if lock acquired
    }
    

First Use Case: Laravel Queue Jobs

Prevent duplicate job execution:

use Symfony\Component\Semaphore\SemaphoreInterface;

class ProcessPaymentJob implements ShouldQueue
{
    public function handle(SemaphoreInterface $semaphore)
    {
        $key = 'payment_' . $this->paymentId;

        if (!$semaphore->acquire($key, 30)) { // 30-second timeout
            throw new \RuntimeException("Payment already being processed");
        }

        try {
            // Process payment
        } finally {
            $semaphore->release();
        }
    }
}

Post-Upgrade Checklist for 5.4.54

  1. composer update symfony/semaphore --with-dependencies (the release also touches polyfill declarations).
  2. composer audit — confirm no remaining advisories for the component.
  3. Re-run queue/cron smoke tests; there is no API change in this release, so no code edits are expected beyond the version constraint.
  4. On legacy hosts still running PHP 7.2, confirm symfony/polyfill-* packages install cleanly after the lockfile bump (see Gotchas).

Implementation Patterns

Core Workflows

  1. Exclusive Locking:

    $semaphore->acquire('resource_key', 10); // Block for 10 seconds
    // Critical section
    $semaphore->release();
    
  2. Shared Locking (Multiple Workers): Use Redis backend for distributed systems:

    $semaphore = new Semaphore(new RedisStore($redisClient));
    
  3. Context Managers (Laravel): Create a reusable trait for jobs:

    trait UsesSemaphore
    {
        protected function acquireSemaphore(string $key, int $timeout = 10): bool
        {
            return app(SemaphoreInterface::class)->acquire($key, $timeout);
        }
    
        protected function withSemaphore(string $key, Closure $callback, int $timeout = 10)
        {
            $semaphore = app(SemaphoreInterface::class);
            $semaphore->acquire($key, $timeout);
    
            try {
                return $callback();
            } finally {
                $semaphore->release();
            }
        }
    }
    

Integration Tips

  • Laravel Queues: Combine with ShouldQueue to prevent overlapping jobs:

    $semaphore->acquire('queue_job_' . $jobId, 60);
    // Job logic
    $semaphore->release();
    
  • API Rate Limiting:

    $semaphore = app(SemaphoreInterface::class);
    $key = 'api_rate_limit_' . $userId . '_' . now()->format('Y-m-d');
    
    if (!$semaphore->acquire($key, 0)) {
        return response()->json(['error' => 'Rate limit exceeded'], 429);
    }
    
    try {
        // Process request
    } finally {
        $semaphore->release();
    }
    
  • Cron Jobs:

    $semaphore = app(SemaphoreInterface::class);
    if ($semaphore->acquire('cron_daily_backup', 0)) {
        // Run backup
        $semaphore->release();
    }
    
  • Database Transactions: Use semaphores for row-level locking when transactions aren’t sufficient:

    $semaphore->acquire('user_' . $userId . '_profile_update');
    try {
        DB::beginTransaction();
        // Update user profile
        DB::commit();
    } finally {
        $semaphore->release();
    }
    

Gotchas and Tips

Pitfalls

  1. Forgetting to Release: Always use finally blocks or context managers to avoid deadlocks:

    // ❌ Dangerous
    if ($semaphore->acquire('key')) {
        // Might throw exception
        $semaphore->release(); // Skipped if exception occurs
    }
    
    // ✅ Safe
    try {
        $semaphore->acquire('key');
        // Critical section
    } finally {
        $semaphore->release();
    }
    
  2. Filesystem Backend Limitations:

    • Only works for single-process applications (e.g., local development).
    • Not distributed-safe: Use Redis for Laravel Forge/VPS or multi-server setups.
  3. Timeouts:

    • Set realistic timeouts (e.g., 30 seconds for queue jobs).
    • Too-short timeouts cause false failures; too-long timeouts risk deadlocks.
  4. Key Collisions:

    • Use unique, descriptive keys (e.g., user_{id}_action_{type}).
    • Avoid generic keys like 'lock'—they cause contention.
  5. Redis Cluster/Sentinel: Ensure your Redis DSN supports clusters/sentinels:

    $semaphore = new Semaphore(new RedisStore($redisClient));
    

    Build the client from your Laravel database.redis config rather than hard-coding a DSN, so cluster/sentinel options carry over.

  6. Version Drift After the Security Release:

    • The ^5.4.x range will happily resolve to a vulnerable patch if your lockfile predates 5.4.54. Enforce the floor:
      "require": { "symfony/semaphore": "^5.4.54" }
      
    • Because 5.4.54 only declares polyfills, a stale composer.lock on a PHP 7.2 host may fail to install — run composer update symfony/semaphore symfony/polyfill-* --with-dependencies once, then redeploy.

Debugging

  • Stuck Locks: Check Redis keys or filesystem locks manually:

    # Redis
    redis-cli KEYS "*semaphore*"
    
    # Filesystem (Linux)
    lsof +D /path/to/locks
    
  • Deadlocks: Use acquire(..., 0) (non-blocking) to check lock status before proceeding.

  • Verifying the Patch Level in Production:

    composer show symfony/semaphore   # expect >= 5.4.54
    php artisan about                 # confirm nothing overrode the lockfile
    

Extension Points

  1. Custom Backends: Implement SemaphoreInterface for specialized storage (e.g., database):

    class DatabaseSemaphore implements SemaphoreInterface
    {
        public function acquire(string $name, int $timeout = null): bool
        {
            // Custom logic (e.g., SELECT FOR UPDATE in PostgreSQL)
        }
        // ...
    }
    
  2. Laravel Binding: Bind the interface once in AppServiceProvider (the component has no Laravel provider of its own):

    public function register()
    {
        $this->app->singleton(SemaphoreInterface::class, function () {
            return new Semaphore(new RedisStore(
                Redis::connection('cache')->client()
            ));
        });
    }
    
  3. Retry Logic: Combine with Laravel’s retry helper for transient failures:

    retry(3, function () use ($semaphore, $key) {
        return $semaphore->acquire($key, 10);
    }, function () {
        // Retry logic
    });
    

Configuration Quirks

  • Redis Connection: Ensure your Laravel Redis config supports the semaphore’s needs:

    'connections' => [
        'semaphore' => [
            'url' => env('REDIS_SEMAPHORE_URL'),
            'options' => [
                'cluster' => env('REDIS_CLUSTER', 'redis'),
            ],
        ],
    ],
    
  • Filesystem Locks: Set storage_path('framework/semaphore') as the lock directory in config/filesystems.php:

    'disks' => [
        'semaphore' => [
            'driver' => 'local',
            'root' => storage_path('framework/semaphore'),
        ],
    ],
    
  • PHP 7.2 / Polyfills: 5.4.54 adds explicit polyfill declarations. Nothing to configure, but symfony/polyfill-php* packages now appear as direct transitive requirements — keep them out of your replace/provide overrides.

Performance Tips

  • Key Design: Use short, predictable keys (e.g., user_123_profile instead of user_profile_123).
  • Batch Processing: For bulk operations, use a single semaphore per batch (e.g., batch_import_2023-10-01).
  • Avoid Nested Locks: Hold locks for the shortest time possible to minimize contention.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log