Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony Lock Component does not lock – how to fix?

i upgraded recently to Symfony 3.4.x, refactor LockHandler because of deprecation warning and fall into strange behaviour.

Code in command before refactoring:

class FooCommand
{
    protected function configure() { /* ... does not matter ... */ }
    protected function lock() : bool
    {
        $resource = $this->getName();
        $lock     = new \Symfony\Component\Filesystem\LockHandler($resource);

        return $lock->lock();
    }
    protected function execute()
    {
        if (!$this->lock()) return 0;

        // Execute some task
    }
}

And it prevents to run two command at the same time – second just finishes without doing job. That is good.

But after suggested refactoring it allows to run many commands simultaneously. This is FAIL. How to prevent execution? New code:

class FooCommand
{
    protected function configure() { /* ... does not matter ... */ }
    protected function lock() : bool
    {
        $resource = $this->getName();
        $store    = new \Symfony\Component\Lock\FlockStore(sys_get_temp_dir());
        $factory  = new \Symfony\Component\Lock\Factory($store);
        $lock     = $factory->createLock($resource);

        return $lock->acquire();
    }
    protected function execute()
    {
        if (!$this->lock()) return 0;

        // Execute some task
    }
}

NB #1: I don't care about many servers or so, just one instance of application.

NB #2: If process was killed then new command must unlock and run.

like image 693
trogwar Avatar asked Dec 12 '17 13:12

trogwar


People also ask

How do I create a lock in Symfony?

Blocking Locks use Symfony\Component\Lock\LockFactory; use Symfony\Component\Lock\Store\RedisStore; $store = new RedisStore(new \Predis\Client('tcp://localhost:6379')); $factory = new LockFactory($store); $lock = $factory->createLock('notification-flush'); $lock->acquire(true);

What is the use of T lock component?

A Treasury lock is a hedging tool used to manage interest-rate risk by effectively securing the current day's interest rates on federal government securities, to cover future expenses that will be financed by borrowing.


2 Answers

You must use the LockableTrait trait

    use Symfony\Component\Console\Command\LockableTrait;
    use Symfony\Component\Console\Command\Command

    class FooCommand  extends Command
    {
        use LockableTrait;
.....
protected function execute(InputInterface $input, OutputInterface $output)
    {
        if (!$this->lock()) {
            $output->writeln('The command is already running in another process.');

            return 0;
        }
// If you prefer to wait until the lock is released, use this:
        // $this->lock(null, true);

        // ...

        // if not released explicitly, Symfony releases the lock
        // automatically when the execution of the command ends
        $this->release();

}
like image 96
Mohamed Ben HEnda Avatar answered Sep 29 '22 03:09

Mohamed Ben HEnda


Adding to Mohamed answer, it's important to assign an id to the command locker.

Otherwise the lock will have concurrency problems with other commands, specially if you have different environments (test, production, stage...). You'll see that the command is not being executed with the expected periodicity.

This id can be assigned on the lock() statement itself.

    use Symfony\Component\Console\Command\LockableTrait;
    use Symfony\Component\Console\Command\Command

    class FooCommand  extends Command
    {
       use LockableTrait;
       .....
       protected function execute(InputInterface $input, OutputInterface $output)
       {
         if (!$this->lock('FooCommand'.getenv('APP_ENV'))) {
            $output->writeln('The command is already running in another process.');
            return 0;
         }

         $this->release();
       }
    }
like image 21
javier_domenech Avatar answered Sep 29 '22 03:09

javier_domenech