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.
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);
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.
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();
}
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();
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With