Skip to content

Commit b53c1ca

Browse files
committed
Add Lock component
This reverts commit e14709d.
1 parent 8b71573 commit b53c1ca

File tree

1 file changed

+258
-0
lines changed

1 file changed

+258
-0
lines changed

components/lock.rst

+258
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
.. index::
2+
single: Lock
3+
single: Components; Lock
4+
5+
The Lock Component
6+
==================
7+
8+
The Lock Component creates and manages `locks`_, a mechanism to provide
9+
exclusive access to a shared resource.
10+
11+
.. versionadded:: 3.4
12+
The Lock component was introduced in Symfony 3.4.
13+
14+
Installation
15+
------------
16+
17+
You can install the component in 2 different ways:
18+
19+
* :doc:`Install it via Composer </components/using_components>` (``symfony/lock`` on `Packagist`_);
20+
* Use the official Git repository (https://github.com/symfony/lock).
21+
22+
.. include:: /components/require_autoload.rst.inc
23+
24+
Usage
25+
-----
26+
27+
Locks are used to guarantee exclusive access to some shared resource. In
28+
Symfony applications, you can use locks for example to ensure that a command is
29+
not executed more than once at the same time (on the same or different servers).
30+
31+
In order to manage the state of locks, a ``Store`` needs to be created first
32+
and then use the :class:`Symfony\\Component\\Lock\\Factory` class to actually
33+
create the lock for some resource::
34+
35+
use Symfony\Component\Lock\Factory;
36+
use Symfony\Component\Lock\Store\SemaphoreStore;
37+
38+
$store = new SemaphoreStore();
39+
$factory = new Factory($store);
40+
41+
Then, a call to the :method:`Symfony\\Component\\Lock\\LockInterface::acquire`
42+
method will try to acquire the lock. Its first argument is an arbitrary string
43+
that represents the locked resource::
44+
45+
// ...
46+
$lock = $factory->createLock('pdf-invoice-generation');
47+
48+
if ($lock->acquire()) {
49+
// The resource "pdf-invoice-generation" is locked.
50+
// You can compute and generate invoice safely here.
51+
52+
$lock->release();
53+
}
54+
55+
If the lock can not be acquired, the method returns ``false``. The ``acquire()``
56+
method can be safely called repeatedly, even if the lock is already acquired.
57+
58+
.. note::
59+
60+
Unlike other implementations, the Lock Component distinguishes locks
61+
instances even when they are created for the same resource. If a lock has
62+
to be used by several services, they should share the same ``Lock`` instance
63+
returned by the ``Factory::createLock`` method.
64+
65+
Blocking Locks
66+
--------------
67+
68+
By default, when a lock cannot be acquired, the ``acquire`` method returns
69+
``false`` immediately. To wait (indefinitely) until the lock
70+
can be created, pass ``true`` as the argument of the ``acquire()`` method. This
71+
is called a **blocking lock** because the execution of your application stops
72+
until the lock is acquired.
73+
74+
Some of the built-in ``Store`` classes support this feature. When they don't,
75+
they can be decorated with the ``RetryTillSaveStore`` class::
76+
77+
use Symfony\Component\Lock\Factory;
78+
use Symfony\Component\Lock\Store\RedisStore;
79+
use Symfony\Component\Lock\Store\RetryTillSaveStore;
80+
81+
$store = new RedisStore(new \Predis\Client('tcp://localhost:6379'));
82+
$store = new RetryTillSaveStore($store);
83+
$factory = new Factory($store);
84+
85+
$lock = $factory->createLock('notification-flush');
86+
$lock->acquire(true);
87+
88+
Expiring Locks
89+
--------------
90+
91+
Locks created remotely are difficult to manage because there is no way for the
92+
remote ``Store`` to know if the locker process is still alive. Due to bugs,
93+
fatal errors or segmentation faults, it cannot be guaranteed that ``release()``
94+
method will be called, which would cause the resource to be locked infinitely.
95+
96+
The best solution in those cases is to create **expiring locks**, which are
97+
released automatically after some amount of time has passed (called TTL for
98+
*Time To Live*). This time, in seconds, is configured as the second argument of
99+
the ``createLock()`` method. If needed, these locks can also be released early
100+
with the ``release()`` method.
101+
102+
The trickiest part when working with expiring locks is choosing the right TTL.
103+
If it's too short, other processes could acquire the lock before finishing the
104+
job; it it's too long and the process crashes before calling the ``release()``
105+
method, the resource will stay locked until the timeout::
106+
107+
// ...
108+
// create an expiring lock that lasts 30 seconds
109+
$lock = $factory->createLock('charts-generation', 30);
110+
111+
$lock->acquire();
112+
try {
113+
// perform a job during less than 30 seconds
114+
} finally {
115+
$lock->release();
116+
}
117+
118+
.. tip::
119+
120+
To avoid letting the lock in a locking state, it's recommended to wrap the
121+
job in a try/catch/finally block to always try to release the expiring lock.
122+
123+
In case of long-running tasks, it's better to start with a not too long TTL and
124+
then use the :method:`Symfony\\Component\\Lock\\LockInterface::refresh` method
125+
to reset the TTL to its original value::
126+
127+
// ...
128+
$lock = $factory->createLock('charts-generation', 30);
129+
130+
$lock->acquire();
131+
try {
132+
while (!$finished) {
133+
// perform a small part of the job.
134+
135+
// renew the lock for 30 more seconds.
136+
$lock->refresh();
137+
}
138+
} finally {
139+
$lock->release();
140+
}
141+
142+
Available Stores
143+
----------------
144+
145+
Locks are created and managed in ``Stores``, which are classes that implement
146+
:class:`Symfony\\Component\\Lock\\StoreInterface`. The component includes the
147+
following built-in store types:
148+
149+
150+
============================================ ====== ======== ========
151+
Store Scope Blocking Expiring
152+
============================================ ====== ======== ========
153+
:ref:`FlockStore <lock-store-flock>` local yes no
154+
:ref:`MemcachedStore <lock-store-memcached>` remote no yes
155+
:ref:`RedisStore <lock-store-redis>` remote no yes
156+
:ref:`SemaphoreStore <lock-store-semaphore>` local yes no
157+
============================================ ====== ======== ========
158+
159+
.. _lock-store-flock:
160+
161+
FlockStore
162+
~~~~~~~~~~
163+
164+
The FlockStore uses the file system on the local computer to create the locks.
165+
It does not support expiration, but the lock is automatically released when the
166+
PHP process is terminated::
167+
168+
use Symfony\Component\Lock\Store\FlockStore;
169+
170+
// the argument is the path of the directory where the locks are created
171+
$store = new FlockStore(sys_get_temp_dir());
172+
173+
.. caution::
174+
175+
Beware that some file systems (such as some types of NFS) do not support
176+
locking. In those cases, it's better to use a directory on a local disk
177+
drive or a remote store based on Redis or Memcached.
178+
179+
.. _lock-store-memcached:
180+
181+
MemcachedStore
182+
~~~~~~~~~~~~~~
183+
184+
The MemcachedStore saves locks on a Memcached server, it requires a Memcached
185+
connection implementing the ``\Memcached`` class. This store does not
186+
support blocking, and expects a TTL to avoid stalled locks::
187+
188+
use Symfony\Component\Lock\Store\MemcachedStore;
189+
190+
$memcached = new \Memcached();
191+
$memcached->addServer('localhost', 11211);
192+
193+
$store = new MemcachedStore($memcached);
194+
195+
.. note::
196+
197+
Memcached does not support TTL lower than 1 second.
198+
199+
.. _lock-store-redis:
200+
201+
RedisStore
202+
~~~~~~~~~~
203+
204+
The RedisStore saves locks on a Redis server, it requires a Redis connection
205+
implementing the ``\Redis``, ``\RedisArray``, ``\RedisCluster`` or
206+
``\Predis`` classes. This store does not support blocking, and expects a TTL to
207+
avoid stalled locks::
208+
209+
use Symfony\Component\Lock\Store\RedisStore;
210+
211+
$redis = new \Redis();
212+
$redis->connect('localhost');
213+
214+
$store = new RedisStore($redis);
215+
216+
.. _lock-store-semaphore:
217+
218+
SemaphoreStore
219+
~~~~~~~~~~~~~~
220+
221+
The SemaphoreStore uses the `PHP semaphore functions`_ to create the locks::
222+
223+
use Symfony\Component\Lock\Store\SemaphoreStore;
224+
225+
$store = new SemaphoreStore();
226+
227+
.. _lock-store-combined:
228+
229+
CombinedStore
230+
~~~~~~~~~~~~~
231+
232+
The CombinedStore is designed for High Availability applications because it
233+
manages several stores in sync (for example, several Redis servers). When a lock
234+
is being acquired, it forwards the call to all the managed stores, and it
235+
collects their responses. If a simple majority of stores have acquired the lock,
236+
then the lock is considered as acquired; otherwise as not acquired::
237+
238+
use Symfony\Component\Lock\Strategy\ConsensusStrategy;
239+
use Symfony\Component\Lock\Store\CombinedStore;
240+
use Symfony\Component\Lock\Store\RedisStore;
241+
242+
$stores = [];
243+
foreach (array('server1', 'server2', 'server3') as $server) {
244+
$redis= new \Redis();
245+
$redis->connect($server);
246+
247+
$stores[] = new RedisStore($redis);
248+
}
249+
250+
$store = new CombinedStore($stores, new ConsensusStrategy());
251+
252+
Instead of the simple majority strategy (``ConsensusStrategy``) an
253+
``UnanimousStrategy`` can be used to require the lock to be acquired in all
254+
the stores.
255+
256+
.. _`locks`: https://en.wikipedia.org/wiki/Lock_(computer_science)
257+
.. _Packagist: https://packagist.org/packages/symfony/lock
258+
.. _`PHP semaphore functions`: http://php.net/manual/en/book.sem.php

0 commit comments

Comments
 (0)