Skip to content

Commit 6469016

Browse files
committed
Add the Lock Component
1 parent f2a40f7 commit 6469016

File tree

1 file changed

+319
-0
lines changed

1 file changed

+319
-0
lines changed

components/lock.rst

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
.. index::
2+
single: Lock
3+
single: Components; Lock
4+
5+
The Lock Component
6+
====================
7+
8+
The Lock Component provides a mechanism to garentee an exclusive access into
9+
a critical section. The component ships with ready to use stores for the
10+
most common backends.
11+
12+
.. versionadded:: 3.3
13+
The Lock component was introduced in Symfony 3.3.
14+
15+
Installation
16+
------------
17+
18+
You can install the component in 2 different ways:
19+
20+
* :doc:`Install it via Composer </components/using_components>` (``symfony/lock`` on `Packagist`_);
21+
* Use the official Git repository (https://github.com/symfony/lock).
22+
23+
.. include:: /components/require_autoload.rst.inc
24+
25+
26+
Usage
27+
-----
28+
29+
In order to centralize state of locks, you first need to create a ``Store``.
30+
Then, you can ask to this store to create a Lock for your ``resource``.
31+
32+
The :method:`Symfony\\Component\\Lock\\LockInterface::acquire` method tries to
33+
acquire the lock. If the lock is can not be acquired, the method throws a
34+
:class:`Symfony\\Component\\Lock\\Exception\\LockConflictedException`. You can
35+
safly call the ``acquire()`` method several time, even if you already acquired
36+
it.
37+
38+
.. code-block:: php
39+
40+
use Symfony\Component\Lock\Store\SemaphoreStore;
41+
use Symfony\Component\Lock\Exception\LockConflictedException;
42+
43+
$store = new SemaphoreStore();
44+
$lock = $store->createLock('hello');
45+
46+
try {
47+
$lock->acquire();
48+
// the resource "hello" is locked. You can perform your task safely.
49+
50+
// do whatever you want.
51+
52+
$lock->release();
53+
} catch (LockConflictedException $e) {
54+
// the resource "hello" is already locked by another process
55+
}
56+
57+
The first argument of `createLock` is a string representation of the
58+
``resource`` to lock.
59+
60+
.. note::
61+
62+
In opposition to some other implementations, the Lock Component distinguish
63+
locks instances, even when they are created from the same ``resource``.
64+
If you want to share a lock in several services. You have to share the
65+
instance of Lock returned by the ``Store::createLock`` method.
66+
67+
68+
Blocking locks
69+
--------------
70+
71+
You can pass an optional blocking argument as the first argument to the
72+
:method:`Symfony\\Component\\Lock\\LockInterface::acquire` method, which
73+
defaults to ``false``. If this is set to ``true``, your PHP code will wait
74+
infinitely until the lock is released by another process.
75+
76+
Some ``Store`` (but not all) natively supports this features. When they don't,
77+
you can decorate it with the ``RetryTillSaveStore``.
78+
79+
.. code-block:: php
80+
81+
use Symfony\Component\Lock\Store\RedisStore;
82+
use Symfony\Component\Lock\Store\RetryTillSaveStore;
83+
84+
$store = new RedisStore(new \Predis\Client('tcp://localhost:6379'));
85+
$store = new RetryTillSaveStore($store);
86+
87+
$lock = $store->createLock('hello');
88+
89+
$lock->acquire(true);
90+
91+
92+
93+
Expirable Locks
94+
---------------
95+
96+
Working with a remote ``Store`` is hard. In oposition to local ``Stores``
97+
(like :ref:`FlockStore <lock-store-flock>` or :ref:`SemaphoreStore <lock-store-semaphore>`) there is now way for the remote
98+
``Store`` to know whether or not the locker process is till alive. Due tu bugs,
99+
fatal errors or segmentation fault, we can't garentee that the ``release()``
100+
function will be called, which would cause a ``resource`` to be locked
101+
infinitely.
102+
103+
To fill this gap, the remote ``Stores`` provide an expirable mechanism: The lock
104+
is acquired for a defined amount of time (named TTL for Time To Live).
105+
When the timeout occured, the lock is automatically released even if the locker
106+
don't call the ``release()`` method.
107+
108+
That's why, when you create a lock on an expirable ``Store``. You have to choose
109+
carrefully the correct TTL. When too low, you take the risk to "loose" the lock
110+
(and someone else acquire it) wheras you don't finish your task.
111+
When too hight and the process crash before you call the ``release()`` method,
112+
the ``resource`` will stay lock till the timeout.
113+
114+
115+
.. code-block:: php
116+
117+
use Symfony\Component\Lock\Store\RedisStore;
118+
119+
$store = new RedisStore(new \Predis\Client('tcp://localhost:6379'));
120+
121+
$lock = $store->createLock('hello', 30);
122+
123+
$lock->acquire();
124+
try {
125+
// perfom a job during less than 30 seconds
126+
} finally {
127+
$lock->release()
128+
}
129+
130+
.. tip::
131+
132+
To avoid to let the Lock in a locking state, try to always release an
133+
expirable lock by wraping the job in a try/catch block for instance.
134+
135+
136+
When you have to work on a really long task, you should not set the TTL to
137+
overlaps the duration of this task. Instead, the Lock Component expose a
138+
:method:`Symfony\\Component\\Lock\\LockInterface::refresh` method in order to
139+
put off the TTL of the Lock. Thereby you can choose a small initial TTL, and
140+
regulary refresh the lock
141+
142+
.. code-block:: php
143+
144+
use Symfony\Component\Lock\Store\RedisStore;
145+
146+
$store = new RedisStore(new \Predis\Client('tcp://localhost:6379'));
147+
148+
$lock = $store->createLock('hello', 30);
149+
150+
$lock->acquire();
151+
try {
152+
while (!$finished) {
153+
// perfom a small part of the job.
154+
155+
$lock->refresh();
156+
// resource is locked for 30 more seconds.
157+
}
158+
} finally {
159+
$lock->release()
160+
}
161+
162+
163+
Available Stores
164+
----------------
165+
166+
``Stores`` are classes that implement :class:`Symfony\\Component\\Lock\\StoreInterface`.
167+
This component provides several adapters ready to use in your applications.
168+
169+
Here is a small summary of availanble ``Stores`` and theire capabilities.
170+
171+
+----------------------------------------------+--------+----------+-----------+
172+
| Store | Scope | Blocking | Expirable |
173+
+==============================================+========+==========+===========+
174+
| :ref:`FlockStore <lock-store-flock>` | local | yes | no |
175+
+----------------------------------------------+--------+----------+-----------+
176+
| :ref:`MemcachedStore <lock-store-memcached>` | remote | no | yes |
177+
+----------------------------------------------+--------+----------+-----------+
178+
| :ref:`RedisStore <lock-store-redis>` | remote | no | yes |
179+
+----------------------------------------------+--------+----------+-----------+
180+
| :ref:`SemaphoreStore <lock-store-semaphore>` | local | yes | no |
181+
+----------------------------------------------+--------+----------+-----------+
182+
183+
.. tip::
184+
185+
Calling the :method:`Symfony\\Component\\Lock\\LockInterface::refresh`
186+
method on a Lock created from a non expirable ``Store`` like
187+
:ref:`FlockStore <lock-store-flock>` will do nothing.
188+
189+
.. _lock-store-flock:
190+
191+
FlockStore
192+
~~~~~~~~~~
193+
194+
The FlockStore use the fileSystem on the local computer to lock and store the
195+
``resource``.
196+
It does not supports expiration, but the lock is automaticaly released when the
197+
PHP process is terminated.
198+
199+
200+
.. code-block:: php
201+
202+
use Symfony\Component\Lock\Store\FlockStore;
203+
204+
$store = new FlockStore(sys_get_temp_dir());
205+
206+
The first argument of the constructor is the path to the directory where the
207+
file will be created.
208+
209+
.. caution::
210+
211+
Beware, some filesystem (like some version of NFS) does not support locking.
212+
We suggest to use local file, or to use a Store dedicated to remote usage
213+
like Redis or Memcached.
214+
215+
216+
.. _Packagist: https://packagist.org/packages/symfony/lock
217+
218+
.. _lock-store-memcached:
219+
220+
MemcachedStore
221+
~~~~~~~~~~~~~~
222+
223+
The MemcachedStore stores state of ``resource`` in a Memcached server. This
224+
``Store`` does not support blocking, and expect a TLL to avoid infinity locks.
225+
226+
.. note::
227+
228+
Memcached does not supports TTL lower than 1 seconds.
229+
230+
231+
It requires to have installed Memcached and have created a connection that
232+
implements the ``\Memcached`` classes::
233+
234+
.. code-block:: php
235+
236+
use Symfony\Component\Lock\Store\RedisStore;
237+
238+
$memcached = new \Memcached();
239+
$memcached->addServer('localhost', 11211);
240+
241+
$store = new MemcachedStore($memcached);
242+
243+
.. _lock-store-redis:
244+
245+
RedisStore
246+
~~~~~~~~~~
247+
248+
The RedisStore uses an instance of Redis to store the state of the ``resource``.
249+
This ``Store`` does not support blocking, and expect a TLL to avoid infinity
250+
locks.
251+
252+
It requires to have installed Redis and have created a connection that
253+
implements the ``\Redis``, ``\RedisArray``, ``\RedisCluster`` or ``\Predis``
254+
classes::
255+
256+
.. code-block:: php
257+
258+
use Symfony\Component\Lock\Store\RedisStore;
259+
260+
$redis = new \Redis();
261+
$redis->connect('localhost');
262+
263+
$store = new RedisStore($redis);
264+
265+
.. _lock-store-semaphore:
266+
267+
SemaphoreStore
268+
~~~~~~~~~~~~~~
269+
270+
The SemaphoreStore uses the PHP semaphore function to lock a ``resources``.
271+
272+
273+
.. code-block:: php
274+
275+
use Symfony\Component\Lock\Store\SemaphoreStore;
276+
277+
$store = new SemaphoreStore($redis);
278+
279+
.. _lock-store-combined:
280+
281+
CombinedStore
282+
~~~~~~~~~~~~~
283+
284+
The CombinedStore synchronize several ``Stores`` together. When it's used to
285+
acquired a Lock, it forward the call to the managed ``Stores``, and regarding the
286+
result, uses a quorum to decide whether or not the lock is acquired.
287+
288+
.. note::
289+
290+
This ``Store`` is usefull for High availability application. You can provide
291+
several Redis Server, and use theses server to manage the Lock. A
292+
MajorityQuorum is enougth to safly acquire a lock while it allow some Redis
293+
server failure.
294+
295+
.. code-block:: php
296+
297+
use Symfony\Component\Lock\Quorum\MajorityQuorum;
298+
use Symfony\Component\Lock\Store\CombinedStore;
299+
use Symfony\Component\Lock\Store\RedisStore;
300+
301+
$stores = [];
302+
foreach (['server1', 'server2', 'server3'] as $server) {
303+
$redis= new \Redis();
304+
$redis->connect($server);
305+
306+
$stores[] = new RedisStore($redis);
307+
}
308+
309+
$store = new CombinedStore($stores, new MajorityQuorum());
310+
311+
312+
.. tip::
313+
314+
You can use the CombinedStore with the UnanimousQuorum to implement chained
315+
``Stores``. It'll allow you to acquire easy local locks before asking for a
316+
remote lock
317+
318+
319+
.. _Packagist: https://packagist.org/packages/symfony/lock

0 commit comments

Comments
 (0)