Weave Code
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

Symfony Semaphore Component provides semaphores for coordinating access to shared resources across processes and threads. Use it to enforce mutual exclusion, limit concurrency, and prevent race conditions via an easy, reusable API.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the Package:

    composer require symfony/semaphore
    

    For Laravel, the facade is pre-installed (Illuminate\Contracts\Bus\QueueingDispatcher uses it under the hood).

  2. Basic Usage (Filesystem Backend):

    use Symfony\Component\Semaphore\Semaphore;
    
    $semaphore = new Semaphore('/path/to/lockfile');
    $semaphore->acquire(); // Blocks until lock is acquired
    try {
        // Critical section
    } finally {
        $semaphore->release(); // Always release!
    }
    
  3. First Use Case:

    • Protect a CLI command from concurrent execution:
      use Symfony\Component\Semaphore\Semaphore;
      
      $semaphore = new Semaphore(storage_path('app/locks/backup.lock'));
      if ($semaphore->acquire(30)) { // Timeout: 30 seconds
          // Run backup logic
          $semaphore->release();
      } else {
          throw new RuntimeException('Backup already in progress');
      }
      

Where to Look First

  • Official Docs: Covers backends (filesystem, Redis), API, and examples.
  • Laravel Integration: Use app('semaphore') or Illuminate\Bus\QueueingDispatcher for built-in queue locking.
  • Redis Backend: For distributed systems, extend RedisSemaphore (see source).

Implementation Patterns

Core Workflows

  1. Short-Lived Locks (e.g., Queue Jobs):

    $semaphore = app('semaphore');
    $semaphore->acquire('job:process_payment', 10); // 10-second timeout
    try {
        // Process payment
    } finally {
        $semaphore->release('job:process_payment');
    }
    
  2. Long-Lived Locks (e.g., Maintenance Mode):

    $semaphore = new Semaphore(storage_path('app/locks/maintenance.lock'));
    if ($semaphore->acquire(0, 3600)) { // No timeout, 1-hour lease
        // Enable maintenance mode
        $semaphore->release(3600); // Auto-release after 1 hour
    }
    
  3. Redis-Backed Distributed Locks:

    use Symfony\Component\Semaphore\RedisSemaphore;
    $redis = new Redis();
    $redis->connect('redis://localhost');
    $semaphore = new RedisSemaphore($redis, 'app_');
    $semaphore->acquire('distributed_task', 5);
    

Integration Tips

  • Laravel Queues: Use the built-in Semaphore via Queue::later() or Bus::dispatch() with Semaphore middleware. Example:

    $job = new ProcessPaymentJob($user);
    Bus::dispatch($job)->onQueue('high')->withSemaphore('payment_processing');
    
  • Database Transactions + Locks: Combine with DB transactions for atomicity:

    DB::transaction(function () use ($semaphore) {
        $semaphore->acquire('inventory_update');
        try {
            // Update inventory
        } finally {
            $semaphore->release('inventory_update');
        }
    });
    
  • Custom Backends: Implement SemaphoreInterface for specialized storage (e.g., Memcached):

    class MemcachedSemaphore implements SemaphoreInterface {
        // ...
    }
    
  • Graceful Degradation: Fallback to a no-op semaphore in tests:

    $semaphore = new class implements SemaphoreInterface {
        public function acquire($name, $timeout = 0, $lease = 0) { return true; }
        public function release($name) {}
    };
    

Gotchas and Tips

Pitfalls

  1. Forgetting to Release:

    • Always use finally blocks or context managers (e.g., Laravel’s Semaphore facade handles this automatically).
    • Fix: Wrap in a try-finally or use a custom SemaphoreContext class.
  2. Filesystem Locks in Distributed Systems:

    • Filesystem locks do not work across servers. Use Redis for multi-server setups.
    • Fix: Configure Redis as the backend:
      $semaphore = new RedisSemaphore($redis, 'app_');
      
  3. Deadlocks:

    • Acquire locks in a consistent order (e.g., alphabetical by lock name).
    • Fix: Log lock acquisition order and monitor for circular dependencies.
  4. Stale Locks:

    • Crashed processes may leave locks unreleased. Use lease times to auto-release:
      $semaphore->acquire('task', 0, 300); // Auto-release after 5 minutes
      
  5. Timeout Misuse:

    • acquire($name, $timeout) blocks for $timeout seconds. Use 0 for indefinite waits.
    • Fix: Prefer acquire($name, 0, $lease) for long-running tasks with auto-release.

Debugging Tips

  • Check Lock Files: Inspect /path/to/lockfile for stale locks (delete manually if needed).
  • Redis Debugging: Use redis-cli to inspect keys:
    redis-cli keys app_*
    
  • Logging: Log lock acquisition/release events:
    $semaphore->acquire('task');
    logger()->info('Acquired lock', ['name' => 'task']);
    

Extension Points

  1. Custom Backends: Extend AbstractSemaphore for new storage (e.g., DynamoDB):

    class DynamoDBSemaphore extends AbstractSemaphore {
        protected function doAcquire($name, $timeout, $lease) { /* ... */ }
        protected function doRelease($name) { /* ... */ }
    }
    
  2. Event Listeners: Hook into lock events (e.g., notify Slack on contention):

    $semaphore->addListener(function ($event) {
        if ($event->isAcquired()) {
            // Log or alert
        }
    });
    
  3. Laravel Service Provider: Bind a custom semaphore to the container:

    $this->app->bind('semaphore', function () {
        return new RedisSemaphore($redis, 'app_');
    });
    

Performance Quirks

  • Filesystem Locks: Slow on NFS mounts. Use Redis for high-throughput systems.
  • Redis Latency: Monitor redis-cli --latency for lock contention.
  • Memory Usage: Redis-backed semaphores consume memory per lock. Clean up unused keys periodically.

Laravel-Specific Tips

  • Queue Semaphores: Laravel’s Queue::later() uses semaphores internally. For custom queues:
    $connection->push('queue', $job, ['semaphore' => 'custom_lock']);
    
  • Artisan Commands: Protect commands with semaphores:
    if (!$semaphore->acquire('command:backup')) {
        $this->error('Command already running.');
        return 1;
    }
    
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.
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui
babelqueue/php-sdk
facebook/capi-param-builder-php
babelqueue/symfony
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle