trogwar
trogwar

Reputation: 120

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.

Upvotes: 6

Views: 5177

Answers (2)

javier_domenech
javier_domenech

Reputation: 6253

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();
       }
    }

Upvotes: 3

Mohamed Ben HEnda
Mohamed Ben HEnda

Reputation: 2766

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();

}

Upvotes: 7

Related Questions