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

Lock Laravel Package

symfony/lock

Symfony Lock component creates and manages locks to ensure exclusive access to shared resources. Provides a unified API with multiple storage backends (e.g., filesystem, Redis, PDO) for preventing race conditions in concurrent apps.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the package:
    composer require symfony/lock
    
  2. Register the LockFactory in Laravel: Add to config/app.php under providers:
    Symfony\Component\Lock\LockFactory::class,
    
    Bind it in AppServiceProvider:
    public function register()
    {
        $this->app->singleton(\Symfony\Component\Lock\LockFactory::class, function ($app) {
            return new \Symfony\Component\Lock\LockFactory([
                new \Symfony\Component\Lock\Store\RedisStore($app['redis']),
            ]);
        });
    }
    
  3. First use case: Protect a critical section in a Laravel job or command:
    use Symfony\Component\Lock\LockFactory;
    
    class ProcessPaymentJob implements ShouldQueue
    {
        public function __construct(private LockFactory $lockFactory) {}
    
        public function handle()
        {
            $lock = $this->lockFactory->createLock('payment_' . $this->paymentId, 30);
            $lock->acquire(function () {
                // Critical section (e.g., update inventory, charge payment)
            });
        }
    }
    

Where to Look First

  • Documentation: Official guide with store implementations (Redis, PDO, Flock) and API reference.
  • LockFactory: Central interface for creating locks with different stores.
  • LockInterface: Core methods (acquire(), release(), isAcquired()).
  • Store classes: Extend or customize Store\*Store (e.g., RedisStore, PdoStore) for your infrastructure.

Implementation Patterns

Core Workflows

1. Blocking Locks (Idempotency)

Use for ensuring only one process executes a critical section (e.g., webhook handling, report generation):

$lock = $lockFactory->createLock('webhook_' . $eventId, 60);
$lock->acquire(function () use ($event) {
    // Process event (e.g., update database, send notifications)
});
  • Timeout: Second argument is TTL (seconds). Set higher for long-running tasks.
  • Automatic release: Lock expires if the process dies or TTL elapses.

2. Non-Blocking Locks (High Throughput)

Use for checking availability without waiting (e.g., rate limiting, cache invalidation):

$lock = $lockFactory->createLock('cache_warmup', 10);
if ($lock->acquireNow()) {
    // Only one process proceeds
    $lock->release();
}

3. Context Managers (Laravel Jobs/Commands)

Wrap lock acquisition/release in a try-finally block:

$lock = $lockFactory->createLock('inventory_update', 30);
$lock->acquire(function () {
    // Critical section
}); // Auto-releases on exit

Or use a custom trait:

trait UsesLocks
{
    protected function withLock(string $name, int $ttl, Closure $callback)
    {
        $lock = $this->lockFactory->createLock($name, $ttl);
        $lock->acquire($callback);
    }
}

4. Store-Specific Patterns

  • Redis:
    $store = new RedisStore($redisClient);
    $lock = $lockFactory->createLock('redis_key', 60, $store);
    
    Optimization: Use RedisStore with Lua scripts for atomic operations.
  • PDO (Database):
    $store = new PdoStore($pdo, 'locks_table');
    
    Tip: Create the table with engine=InnoDB (MySQL) for row-level locking.
  • Filesystem (Flock):
    $store = new FlockStore('/tmp/locks');
    
    Caveat: Not reliable across network mounts (use for local dev only).

Integration Tips

Laravel Jobs

Inject LockFactory into jobs and use acquire() in handle():

class ExportReportJob implements ShouldQueue
{
    public function __construct(private LockFactory $lockFactory) {}

    public function handle()
    {
        $this->lockFactory->createLock('export_report', 3600)
            ->acquire(fn () => $this->generateReport());
    }
}

Console Commands

Protect commands from overlapping execution:

class GenerateSitemapCommand extends Command
{
    protected $signature = 'sitemap:generate';
    protected $description = 'Generate sitemap.xml';

    public function handle(LockFactory $lockFactory)
    {
        $lock = $lockFactory->createLock('sitemap_generation', 1800);
        $lock->acquire(function () {
            // Generate and save sitemap
        });
    }
}

Queue Workers

Prevent duplicate processing in failed() or retry() logic:

$lock = $lockFactory->createLock('retry_' . $jobId, 60);
if ($lock->acquireNow()) {
    $this->retryJob();
    $lock->release();
}

Dynamic Lock Names

Use unique identifiers (e.g., user_{id}_action):

$lock = $lockFactory->createLock("user_{$userId}_update_profile", 30);

Fallback Stores

Configure multiple stores and fall back gracefully:

$lockFactory = new LockFactory([
    new RedisStore($redis),
    new FlockStore('/tmp/locks'), // Fallback
]);

Gotchas and Tips

Pitfalls

  1. Deadlocks

    • Cause: Circular dependencies (e.g., Job A locks X and waits for Y, while Job B locks Y and waits for X).
    • Fix: Enforce a global lock acquisition order (e.g., alphabetical by resource name).
    • Debug: Use LockInterface::isAcquired() to check lock status before acquiring.
  2. Lock Expiration Too Short

    • Cause: Long-running tasks (e.g., 5-minute reports) with TTL=60 seconds.
    • Fix: Set TTL to 1.5x the expected runtime (e.g., 150s for 100s tasks).
  3. Store-Specific Issues

    • Redis: Network partitions may cause "lost" locks. Use RedisStore with retryOnFailure().
    • PDO: MySQL InnoDB deadlocks can occur. Add FOR UPDATE hints in custom queries.
    • Flock: Unreliable on NFS/network drives. Avoid in production.
  4. Key Collisions

    • Cause: Different keys resolve to the same underlying lock (e.g., hashing conflicts).
    • Fix: Use a LockKeyNormalizer (Symfony 7.4+) or prefix keys:
      $lock = $lockFactory->createLock("app:{$uniqueId}", 30);
      
  5. Memory Leaks

    • Cause: Unreleased locks in long-running processes (e.g., Lumen micro-services).
    • Fix: Use try-finally or Laravel’s illuminate/support ensureClosed():
      $lock->acquire(function () use ($lock) {
          try {
              // Work
          } finally {
              $lock->release(); // Ensure release
          }
      });
      

Debugging

  • Check Lock Status:
    if ($lock->isAcquired()) {
        echo "Lock held by PID: " . $lock->getOwnerPid();
    }
    
  • Redis Debugging:
    redis-cli monitor  # Watch lock commands in real-time
    redis-cli ttl <key> # Check remaining TTL
    
  • PDO Debugging:
    SELECT * FROM locks_table WHERE name = 'your_lock';
    
  • Log Lock Events:
    $lockFactory = new LockFactory([], [
        'logger' => $this->app->make(\Psr\Log\LoggerInterface::class),
    ]);
    

Configuration Quirks

  1. Redis Connection

    • Ensure the Redis client is configured with pipelining disabled for atomicity:
      $redis = new \Redis();
      $redis->pconnect('127.0.0.1');
      $store = new RedisStore($redis, 'locks', 0, 0, false); // disablePipelining = true
      
  2. PDO Table Schema

    • Create the table with ENGINE=InnoDB (MySQL) for row-level locking:
      CREATE
      
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.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport