-
-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Add docs for the Lock component #7364
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6469016
60bfe03
d1d3b71
2498ccd
6b6d865
17248e1
d6a218c
d4326c0
3035fe9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,258 @@ | ||
.. index:: | ||
single: Lock | ||
single: Components; Lock | ||
|
||
The Lock Component | ||
================== | ||
|
||
The Lock Component creates and manages `locks`_, a mechanism to provide | ||
exclusive access to a shared resource. | ||
|
||
.. versionadded:: 3.3 | ||
The Lock component was introduced in Symfony 3.3. | ||
|
||
Installation | ||
------------ | ||
|
||
You can install the component in 2 different ways: | ||
|
||
* :doc:`Install it via Composer </components/using_components>` (``symfony/lock`` on `Packagist`_); | ||
* Use the official Git repository (https://github.com/symfony/lock). | ||
|
||
.. include:: /components/require_autoload.rst.inc | ||
|
||
Usage | ||
----- | ||
|
||
Locks are used to guarantee exclusive access to some shared resource. In | ||
Symfony applications, you can use locks for example to ensure that a command is | ||
not executed more than once at the same time (on the same or different servers). | ||
|
||
In order to manage the state of locks, a ``Store`` needs to be created first | ||
and then use the :class:`Symfony\\Component\\Lock\\Factory` class to actually | ||
create the lock for some resource:: | ||
|
||
use Symfony\Component\Lock\Factory; | ||
use Symfony\Component\Lock\Store\SemaphoreStore; | ||
|
||
$store = new SemaphoreStore(); | ||
$factory = new Factory($store); | ||
|
||
Then, a call to the :method:`Symfony\\Component\\Lock\\LockInterface::acquire` | ||
method will try to acquire the lock. Its first argument is an arbitrary string | ||
that represents the locked resource:: | ||
|
||
// ... | ||
$lock = $factory->createLock('pdf-invoice-generation'); | ||
|
||
if ($lock->acquire()) { | ||
// The resource "pdf-invoice-generation" is locked. | ||
// You can compute and generate invoice safely here. | ||
|
||
$lock->release(); | ||
} | ||
|
||
If the lock can not be acquired, the method returns ``false``. The ``acquire()`` | ||
method can be safely called repeatedly, even if the lock is already acquired. | ||
|
||
.. note:: | ||
|
||
Unlike other implementations, the Lock Component distinguishes locks | ||
instances even when they are created for the same resource. If a lock has | ||
to be used by several services, they should share the same ``Lock`` instance | ||
returned by the ``Factory::createLock`` method. | ||
|
||
Blocking Locks | ||
-------------- | ||
|
||
By default, when a lock cannot be acquired, the ``acquire`` method returns | ||
``false`` immediately. To wait (indefinitely) until the lock | ||
can be created, pass ``true`` as the argument of the ``acquire()`` method. This | ||
is called a **blocking lock** because the execution of your application stops | ||
until the lock is acquired. | ||
|
||
Some of the built-in ``Store`` classes support this feature. When they don't, | ||
they can be decorated with the ``RetryTillSaveStore`` class:: | ||
|
||
use Symfony\Component\Lock\Factory; | ||
use Symfony\Component\Lock\Store\RedisStore; | ||
use Symfony\Component\Lock\Store\RetryTillSaveStore; | ||
|
||
$store = new RedisStore(new \Predis\Client('tcp://localhost:6379')); | ||
$store = new RetryTillSaveStore($store); | ||
$factory = new Factory($store); | ||
|
||
$lock = $factory->createLock('notification-flush'); | ||
$lock->acquire(true); | ||
|
||
Expiring Locks | ||
-------------- | ||
|
||
Locks created remotely are difficult to manage because there is no way for the | ||
remote ``Store`` to know if the locker process is still alive. Due to bugs, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. locking process? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In fact, by |
||
fatal errors or segmentation faults, it cannot be guaranteed that ``release()`` | ||
method will be called, which would cause the resource to be locked infinitely. | ||
|
||
The best solution in those cases is to create **expiring locks**, which are | ||
released automatically after some amount of time has passed (called TTL for | ||
*Time To Live*). This time, in seconds, is configured as the second argument of | ||
the ``createLock()`` method. If needed, these locks can also be released early | ||
with the ``release()`` method. | ||
|
||
The trickiest part when working with expiring locks is choosing the right TTL. | ||
If it's too short, other processes could acquire the lock before finishing the | ||
job; it it's too long and the process crashes before calling the ``release()`` | ||
method, the resource will stay locked until the timeout:: | ||
|
||
// ... | ||
// create an expiring lock that lasts 30 seconds | ||
$lock = $factory->createLock('charts-generation', 30); | ||
|
||
$lock->acquire(); | ||
try { | ||
// perform a job during less than 30 seconds | ||
} finally { | ||
$lock->release(); | ||
} | ||
|
||
.. tip:: | ||
|
||
To avoid letting the lock in a locking state, it's recommended to wrap the | ||
job in a try/catch/finally block to always try to release the expiring lock. | ||
|
||
In case of long-running tasks, it's better to start with a not too long TTL and | ||
then use the :method:`Symfony\\Component\\Lock\\LockInterface::refresh` method | ||
to reset the TTL to its original value:: | ||
|
||
// ... | ||
$lock = $factory->createLock('charts-generation', 30); | ||
|
||
$lock->acquire(); | ||
try { | ||
while (!$finished) { | ||
// perform a small part of the job. | ||
|
||
// renew the lock for 30 more seconds. | ||
$lock->refresh(); | ||
} | ||
} finally { | ||
$lock->release(); | ||
} | ||
|
||
Available Stores | ||
---------------- | ||
|
||
Locks are created and managed in ``Stores``, which are classes that implement | ||
:class:`Symfony\\Component\\Lock\\StoreInterface`. The component includes the | ||
following built-in store types: | ||
|
||
|
||
============================================ ====== ======== ======== | ||
Store Scope Blocking Expiring | ||
============================================ ====== ======== ======== | ||
:ref:`FlockStore <lock-store-flock>` local yes no | ||
:ref:`MemcachedStore <lock-store-memcached>` remote no yes | ||
:ref:`RedisStore <lock-store-redis>` remote no yes | ||
:ref:`SemaphoreStore <lock-store-semaphore>` local yes no | ||
============================================ ====== ======== ======== | ||
|
||
.. _lock-store-flock: | ||
|
||
FlockStore | ||
~~~~~~~~~~ | ||
|
||
The FlockStore uses the file system on the local computer to create the locks. | ||
It does not support expiration, but the lock is automatically released when the | ||
PHP process is terminated:: | ||
|
||
use Symfony\Component\Lock\Store\FlockStore; | ||
|
||
// the argument is the path of the directory where the locks are created | ||
$store = new FlockStore(sys_get_temp_dir()); | ||
|
||
.. caution:: | ||
|
||
Beware that some file systems (such as some types of NFS) do not support | ||
locking. In those cases, it's better to use a directory on a local disk | ||
drive or a remote store based on Redis or Memcached. | ||
|
||
.. _lock-store-memcached: | ||
|
||
MemcachedStore | ||
~~~~~~~~~~~~~~ | ||
|
||
The MemcachedStore saves locks on a Memcached server, it requires a Memcached | ||
connection implementing the ``\Memcached`` class. This store does not | ||
support blocking, and expects a TTL to avoid stalled locks:: | ||
|
||
use Symfony\Component\Lock\Store\MemcachedStore; | ||
|
||
$memcached = new \Memcached(); | ||
$memcached->addServer('localhost', 11211); | ||
|
||
$store = new MemcachedStore($memcached); | ||
|
||
.. note:: | ||
|
||
Memcached does not support TTL lower than 1 second. | ||
|
||
.. _lock-store-redis: | ||
|
||
RedisStore | ||
~~~~~~~~~~ | ||
|
||
The RedisStore saves locks on a Redis server, it requires a Redis connection | ||
implementing the ``\Redis``, ``\RedisArray``, ``\RedisCluster`` or | ||
``\Predis`` classes. This store does not support blocking, and expects a TTL to | ||
avoid stalled locks:: | ||
|
||
use Symfony\Component\Lock\Store\RedisStore; | ||
|
||
$redis = new \Redis(); | ||
$redis->connect('localhost'); | ||
|
||
$store = new RedisStore($redis); | ||
|
||
.. _lock-store-semaphore: | ||
|
||
SemaphoreStore | ||
~~~~~~~~~~~~~~ | ||
|
||
The SemaphoreStore uses the `PHP semaphore functions`_ to create the locks:: | ||
|
||
use Symfony\Component\Lock\Store\SemaphoreStore; | ||
|
||
$store = new SemaphoreStore(); | ||
|
||
.. _lock-store-combined: | ||
|
||
CombinedStore | ||
~~~~~~~~~~~~~ | ||
|
||
The CombinedStore is designed for High Availability applications because it | ||
manages several stores in sync (for example, several Redis servers). When a lock | ||
is being acquired, it forwards the call to all the managed stores, and it | ||
collects their responses. If a simple majority of stores have acquired the lock, | ||
then the lock is considered as acquired; otherwise as not acquired:: | ||
|
||
use Symfony\Component\Lock\Strategy\ConsensusStrategy; | ||
use Symfony\Component\Lock\Store\CombinedStore; | ||
use Symfony\Component\Lock\Store\RedisStore; | ||
|
||
$stores = []; | ||
foreach (array('server1', 'server2', 'server3') as $server) { | ||
$redis= new \Redis(); | ||
$redis->connect($server); | ||
|
||
$stores[] = new RedisStore($redis); | ||
} | ||
|
||
$store = new CombinedStore($stores, new ConsensusStrategy()); | ||
|
||
Instead of the simple majority strategy (``ConsensusStrategy``) an | ||
``UnanimousStrategy`` can be used to require the lock to be acquired in all | ||
the stores. | ||
|
||
.. _`locks`: https://en.wikipedia.org/wiki/Lock_(computer_science) | ||
.. _Packagist: https://packagist.org/packages/symfony/lock | ||
.. _`PHP semaphore functions`: http://php.net/manual/en/book.sem.php |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"you can use locks" => "locks can be used"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be redundant with the first sentence
Locks are used...
don't you think?