phpsymfonylockingsymfony4

Symfony, set a lock in a command to lock another command


On Symfony 4.4, I am trying to establish a lock in a command and keep it alive until explicitly released. Reading the docs I understand that this should be obtained with the $autoRelease param of Symfony\Component\Lock\LockFactory::createLock(), but it seems that even if I set $autoRelease = false the lock is automatically released when the script ends.

LockCommand

<?php
namespace App\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Lock\LockFactory;


class LockCommand extends Command
{
    const LOCK_NAME = "my_lock";

    protected static $defaultName = 'app:lock';

    private $lockFactory;

    public function __construct(LockFactory $lockFactory)
    {
        $this->lockFactory = $lockFactory;
        parent::__construct();
    }    

    protected function configure()
    {
        $this
            ->setDescription("Enables/disables the lock.")
            ->addOption(
                'release',
                'r',
                InputOption::VALUE_NONE,
                'Releases the lock'
            )
        ;
    }    

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $lock = $this->lockFactory->createLock(self::LOCK_NAME, 300.0, false);

        $release = $input->getOption('release');
        
        if ($release) {
            $lock->release();
        } else {
            $lock->acquire();
        }

        //sleep(300);

        return 0;
    }    
}

LockVerifyCommand

<?php
namespace App\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Lock\LockFactory;

class LockVerifyCommand extends Command
{
    protected static $defaultName = 'app:lock-verify';

    private $lockFactory;

    public function __construct(LockFactory $lockFactory)
    {
        $this->lockFactory = $lockFactory;
        parent::__construct();
    }    

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $lock = $this->lockFactory->createLock(LockCommand::LOCK_NAME, 300.0, false);
        $status = $lock->acquire() ? "no" : "yes";
        $output->writeln("Locked? $status");

        return 0;
    }
}

I would expect to be able to

  1. enable the lock php bin/console app:lock
  2. php bin/console app:lock-verify should say Locked? yes
  3. until I release the lock php bin/console app:lock --release

this happens only while the app:lock command is running.


Solution

  • The most important thing here is the actual implementation of LockStore. Depending on which one you choose can mean a whole lot of difference.

    For example, if I am not mistaken, the LockStore is SemaphoreStore by default. You can look up the code on GitHub, but the main thing to notice is that it uses PHP's ext-semaphore extension (that is, sem_* functions). Looking into the PHP's doc page for sem_acquire, it says:

    After processing a request, any semaphores acquired by the process but not explicitly released will be released automatically and a warning will be generated.

    In other words, the lock is not persisted after the main process ends.

    The same goes for FileStore, though it is entirely up to OS to release the lock, which can get messy.

    What you need is some persistent store, like PdoStore, DoctrineDbalStore, RedisStore, or something else.

    Just beware that some stores auto-expire locks. You can read more about that in the official docs page: Expiring Locks