File locking is a mechanism that restricts access to a computer file by allowing only one user or process access at any specific time. This mechanism was introduced back in 1963 for the mainframes and it will make its debut in Symfony starting from version 2.6.
The new LockHandler
class provides a simple abstraction to lock anything by
means of a file lock. Its most common use case is to avoid race conditions by
locking commands, so the same command cannot be executed concurrently by
different processes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Filesystem\LockHandler;
class UpdateContentsCommand extends Command
{
protected function configure()
{
// ...
}
protected function execute(InputInterface $input, OutputInterface $output)
{
// create the lock
$lock = new LockHandler('update:contents');
if (!$lock->lock()) {
$output->writeln('The command is already running in another process.');
return 0;
}
// ... do some task
// (optional) release the lock (otherwise, PHP will do it
// for you automatically)
$lock->release();
}
}
The LockHandler
constructor takes as its first argument the lock identifier,
which will be used as part of the name of the file used to create the lock. By
default, locks are created in the temporary directory of the system. If you want
to use a specific directory, pass it as the second optional argument of the
constructor.
The lock()
method returns true
if the lock was acquired and false
otherwise. In addition, you can optionally pass a boolean argument to indicate
if you want to wait until the requested lock is released. This is useful to
execute a command just after the other locking command is finished:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
class UpdateContentsCommand extends Command
{
// ...
protected function execute(InputInterface $input, OutputInterface $output)
{
// create the lock
$lock = new LockHandler('update:contents');
// wait for the lock release as long as necessary
if (!$lock->lock(true)) {
$output->writeln('The command is already running in another process.');
return 0;
}
// ...
}
}
The lock handler has been limited on purpose to work only with file-based locks, because it's extremely complex to make it work on network or databases. This means that it only works when using one and only one host. If you have several hosts, you must not use this helper.
Thank you for this improvement!
Very useful feature! Thank you!
Great addition, well done! :) Tnx
Thank you. Never thought that this has to be built in to Symfony. I simply used my own lock files. But this is cleaner.
Nice helper! Could you add the
use
statement for the LockHandler class in your example? Right now we can't see the namespace of this class ;)@Loick thanks for your suggestion! I've just added the class import (use Symfony\Component\Filesystem\LockHandler;) to the code block. Indeed this is an important thing, because it was one of the hot topics of the discussion (at first it was put in the Console component, then it was suggested to add it to the Process component and at the end it was included in the Filesystem component).
@javier I didn't know that, thanks. One last thing, the method is called
release
notunlock
(it probably changed during the development as the example of the PR is not up-to-date too with the final implementation) ;)This really should be an API where the implementation is abstracted.
I don't have a need for local filesystem locking, but multiple servers via database or virtual filesystem would be quite handy.
@Loick thanks for reporting this error. I've just updated the method name to
release()
, which by the way makes more sense to me.For you information, the "release" method is optional, because PHP will lease the lock at the end of the script execution.
Great!
On the second example, why would you write the if, it's waiting?
@Roger, with a blocking lock, the command will wait, but there could be some problems that prevent getting the lock. In those cases the command won't wait forever and it will return false, as seen in these lines of code: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Filesystem/LockHandler.php#L82-87
What will happen if the PHP script fails, fx the server crashes. Wouldnt it be a good idea if the lock got released after a certain time?
$lock = new LockHandler('update:contents', 3600); // The lock will auto release after 3600 sec.
Have you thought about extending it to work across hosts, and to be independent of the file system? You could e.g. use memcached or redis to do atomic locks. Would you consider adding it?
Didn't see that one coming, great, thank-you ! :)
So many years doing myself and now... it's in the core. Thanks!!!
Nice.
Any reason why this was implemented directly as a concrete class rather than an interface? Thinking about the mentioned, currently unsupported use-cases of locking using something other than a file.
Great! I agree with @LainPotter. It would be cool to implement a LockHandler using Doctrine transactions: https://gist.github.com/xanderzhang/2729873 or any other method.
Please make an implementation (or two) of this which works across different hosts. On a single machine it is nice, but adds limited value, as it is not too hard to implement on your own...
Guys, stof just told me on github that it was a decision not to include distributed locking in core:-
https://github.com/symfony/symfony/pull/10475
Nice one ;) Is it or will it be available as autonomous component?