55The Lock Component
66==================
77
8- The Lock Component provides a mechanism to guarantee an exclusive access
9- into a critical section. The component ships with ready to use stores for
10- the most common backends.
8+ The Lock Component creates and manages `locks `_, a mechanism to provide
9+ exclusive access to a shared resource.
1110
1211.. versionadded :: 3.3
1312 The Lock component was introduced in Symfony 3.3.
@@ -25,134 +24,118 @@ You can install the component in 2 different ways:
2524Usage
2625-----
2726
28- In order to centralize state of locks, you first need to create a `` Store ``.
29- Then , you can use the :class: ` Symfony \\ Component \\ Lock \\ Factory ` to create a
30- Lock for your `` resource `` .
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) .
3130
32- The :method: `Symfony\\ Component\\ Lock\\ LockInterface::acquire ` method tries to
33- acquire the lock. If the lock can not be acquired, the method returns ``false ``.
34- You can safely call the ``acquire() `` method several times, even if you already
35- acquired it.
36-
37- .. code-block :: php
31+ In order to manage the state of locks, you first need to create a ``Store ``
32+ and then use the :class: `Symfony\\ Component\\ Lock\\ Factory ` class to actually
33+ create the lock for some resource::
3834
3935 use Symfony\Component\Lock\Factory;
4036 use Symfony\Component\Lock\Store\SemaphoreStore;
4137
4238 $store = new SemaphoreStore();
4339 $factory = new Factory($store);
44- $lock = $factory->createLock('invoice-pdf-generation');
4540
46- if ($lock->acquire()) {
47- // the resource "invoice-pdf-generation" is locked.
41+ Then, call to the :method: `Symfony\\ Component\\ Lock\\ LockInterface::acquire `
42+ method to 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');
4847
48
8000
+ if ($lock->acquire()) {
49+ // The resource "pdf-invoice-generation" is locked.
4950 // You can compute and generate invoice safely here.
5051
5152 $lock->release();
5253 }
5354
54- The first argument of `` createLock `` is a string representation of the
55- `` resource `` to lock .
55+ If the lock can not be acquired, the method returns `` false ``. You can safely
56+ call the `` acquire() `` method repeatedly, even if you already acquired it .
5657
5758.. note ::
5859
59- In opposition to some other implementations, the Lock Component
60- distinguishes locks instances, even when they are created from the same
61- ``resource ``.
62- If you want to share a lock in several services. You have to share the
63- instance of Lock returned by the ``Factory::createLock `` method.
60+ Unlike other implementations, the Lock Component distinguishes locks
61+ instances even when they are created for the same resource. If you want to
62+ share a lock in several services, share the ``Lock `` instance returned by
63+ the ``Factory::createLock `` method.
6464
<
6D40
/code>6565Blocking Locks
6666--------------
6767
68- You can pass an optional blocking argument as the first argument to the
69- :method: `Symfony\\ Component\\ Lock\\ LockInterface::acquire ` method, which
70- defaults to ``false ``. If this is set to ``true ``, your PHP code will wait
71- infinitely until the lock is released by another process.
72-
73- Some ``Store `` (but not all) natively supports this feature. When they don't,
74- you can decorate it with the ``RetryTillSaveStore ``.
68+ By default, when a lock cannot be acquired, the ``acquire `` method returns
69+ ``false `` immediately. In case you want to wait (indefinitely) until the lock
70+ can be created, pass ``false `` 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.
7573
76- .. code-block :: php
74+ Some of the built-in ``Store `` classes support this feature. When they don't,
75+ you can decorate them with the ``RetryTillSaveStore `` class::
7776
7877 use Symfony\Component\Lock\Factory;
7978 use Symfony\Component\Lock\Store\RedisStore;
8079 use Symfony\Component\Lock\Store\RetryTillSaveStore;
8180
8281 $store = new RedisStore(new \Predis\Client('tcp://localhost:6379'));
8382 $store = new RetryTillSaveStore($store);
84-
8583 $factory = new Factory($store);
8684
8785 $lock = $factory->createLock('notification-flush');
88-
8986 $lock->acquire(true);
9087
9188Expiring Locks
9289--------------
9390
94- Working with a remote ``Store `` is hard. There is no way for the remote
95- ``Store `` to know if the locker process is still alive.
96- Due to bugs, fatal errors or segmentation fault, we can't guarantee that the
97- ``release() `` method will be called, which would cause a ``resource `` to be locked
98- infinitely.
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, we can't guarantee that the ``release() ``
94+ method will be called, which would cause the resource to be locked infinitely.
9995
100- To fill this gap, the remote ``Store `` provide an expiration mechanism: The
101- lock is acquired for a defined amount of time (named TTL for Time To Live).
102- When the timeout occurs, the lock is automatically released even if the locker
103- don't call the ``release() `` method.
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.
104101
105- That's why, when you create a lock on an expiring ``Store ``, you have to choose
106- carefully the correct TTL. When too low, you take the risk to "loose" the lock
107- (and someone else acquire it) whereas you don't finish your task.
108- When too high and the process crash before you call the ``release() `` method,
109- the ``resource `` will stay lock till the timeout.
110-
111- .. code-block :: php
112-
113- use Symfony\Component\Lock\Factory;
114- use Symfony\Component\Lock\Store\RedisStore;
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 your
104+ work; it it's too long and the process crashes before calling the ``release() ``
105+ method, the resource will stay locked until the timeout::
115106
116- $store = new RedisStore(new \Predis\Client('tcp://localhost:6379'));
117-
118- $factory = new Factory($store);
107+ // ...
108+ // create a expiring lock that lasts 30 seconds
119109 $lock = $factory->createLock('charts-generation', 30);
120110
121111 $lock->acquire();
122112 try {
123- // perfom a job during less than 30 seconds
113+ // perform a job during less than 30 seconds
124114 } finally {
125115 $lock->release();
126116 }
127117
128118.. tip ::
129119
130- To avoid letting the Lock in a locking state, try to always release an
131- expiring lock by wrapping the job in a try/catch block for instance .
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 .
132122
133- When you have to work on a really long task, you should not set the TTL to
134- overlap the duration of this task. Instead, the Lock Component exposes a
135- :method: `Symfony\\ Component\\ Lock\\ LockInterface::refresh ` method in order to
136- put off the TTL of the Lock. Thereby you can choose a small initial TTL, and
137- regularly refresh the lock.
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::
138126
139127.. code-block :: php
140128
141- use Symfony\Component\Lock\Factory;
142- use Symfony\Component\Lock\Store\RedisStore;
143-
144- $store = new RedisStore(new \Predis\Client('tcp://localhost:6379'));
145-
146- $factory = new Factory($store);
129+ // ...
147130 $lock = $factory->createLock('charts-generation', 30);
148131
149132 $lock->acquire();
150133 try {
151134 while (!$finished) {
152135 // perform a small part of the job.
153136
137+ // renew the lock for 30 more seconds.
154138 $lock->refresh();
155- // resource is locked for 30 more seconds.
156139 }
157140 } finally {
158141 $lock->release();
@@ -161,71 +144,48 @@ regularly refresh the lock.
161144 Available Stores
162145----------------
163146
164- ``Stores `` are classes that implement :class: `Symfony\\ Component\\ Lock\\ StoreInterface `.
165- This component provides several adapters ready to use in your applications.
166-
167- Here is a small summary of available ``Stores `` and their capabilities.
147+ Locks are created and managed in ``Stores ``, which are classes that implement
148+ :class: `Symfony\\ Component\\ Lock\\ StoreInterface `. The component includes the
149+ following built-in store types:
168150
169- +----------------------------------------------+--------+----------+----------+
170- | Store | Scope | Blocking | Expiring |
171- +==============================================+========+==========+==========+
172- | :ref: `FlockStore <lock-store-flock >` | local | yes | no |
173- +----------------------------------------------+--------+----------+----------+
174- | :ref: `MemcachedStore <lock-store-memcached >` | remote | no | yes |
175- +----------------------------------------------+--------+----------+----------+
176- | :ref: `RedisStore <lock-store-redis >` | remote | no | yes |
177- +----------------------------------------------+--------+----------+----------+
178- | :ref: `SemaphoreStore <lock-store-semaphore >` | local | yes | no |
179- +----------------------------------------------+--------+----------+----------+
180151
181- .. tip ::
182-
183- Calling the :method: `Symfony\\ Component\\ Lock\\ LockInterface::refresh `
184- method on a Lock created from a non expiring ``Store `` like
185- :ref: `FlockStore <lock-store-flock >` will do nothing.
152+ ============================================ ====== ======== ========
153+ Store Scope Blocking Expiring
154+ ============================================ ====== ======== ========
155+ :ref: `FlockStore <lock-store-flock >` local yes no
156+ :ref: `MemcachedStore <lock-store-memcached >` remote no yes
157+ :ref: `RedisStore <lock-store-redis >` remote no yes
158+ :ref: `SemaphoreStore <lock-store-semaphore >` local yes no
159+ ============================================ ====== ======== ========
186160
187161.. _lock-store-flock :
188162
189163FlockStore
190164~~~~~~~~~~
191165
192- The FlockStore uses the fileSystem on the local computer to lock and store the
193- ``resource ``. It does not support expiration, but the lock is automatically
194- released when the PHP process is terminated.
195-
196- .. code-block :: php
166+ The FlockStore uses the file system on the local computer to create the locks.
167+ It does not support expiration, but the lock is automatically released when the
168+ PHP process is terminated::
197169
198170 use Symfony\Component\Lock\Store\FlockStore;
199171
172+ // the argument is the path of the directory where the locks are created
200173 $store = new FlockStore(sys_get_temp_dir());
201174
202- The first argument of the constructor is the path to the directory where the
203- file will be created.
204-
205175.. caution ::
206176
207- Beware, some filesystems (like some version of NFS) do not support locking.
208- We suggest to use local file, or to use a Store dedicated to remote usage
209- like Redis or Memcached.
210-
211- .. _Packagist : https://packagist.org/packages/symfony/lock
177+ Beware that some file systems (such as some types of NFS) do not support
178+ locking. In those cases, it's better to use a local file or a remote store
179+ based on Redis or Memcached.
212180
213181.. _lock-store-memcached :
214182
215183MemcachedStore
216184~~~~~~~~~~~~~~
217185
218- The MemcachedStore stores state of ``resource `` in a Memcached server. This
219- ``Store `` does not support blocking, and expects a TLL to avoid infinity locks.
220-
221- .. note ::
222-
223- Memcached does not support TTL lower than 1 second.
224-
225- It requires to have installed Memcached and have created a connection that
226- implements the ``\Memcached `` class.
227-
228- .. code-block :: php
186+ The MemcachedStore saves locks on a Memcached server, so first you must create
187+ a Memcached connection implements the ``\Memcached `` class. This store does not
188+ support blocking, and expects a TTL to avoid stalled locks::
229189
230190 use Symfony\Component\Lock\Store\MemcachedStore;
231191
@@ -234,20 +194,19 @@ implements the ``\Memcached`` class.
234194
235195 $store = new MemcachedStore($memcached);
236196
197+ .. note ::
198+
199+ Memcached does not support TTL lower than 1 second.
200+
237201.. _lock-store-redis :
238202
239203RedisStore
240204~~~~~~~~~~
241205
242- The RedisStore uses an instance of Redis to store the state of the ``resource ``.
243- This ``Store `` does not support blocking, and expect a TLL to avoid infinity
244- locks.
245-
246- It requires to have installed Redis and have created a connection that
247- implements the ``\Redis ``, ``\RedisArray ``, ``\RedisCluster `` or ``\Predis ``
248- classes
249-
250- .. code-block :: php
206+ The RedisStore saves locks on a Redis server, so first you must create a Redis
207+ connection implements the ``\Redis ``, ``\RedisArray ``, ``\RedisCluster `` or
208+ ``\Predis `` classes. This store does not support blocking, and expects a TTL to
209+ avoid stalled locks::
251210
252211 use Symfony\Component\Lock\Store\RedisStore;
253212
@@ -261,9 +220,7 @@ classes
261220SemaphoreStore
262221~~~~~~~~~~~~~~
263222
264- The SemaphoreStore uses the PHP semaphore functions to lock a ``resources ``.
265-
266- .. code-block :: php
223+ The SemaphoreStore uses the `PHP semaphore functions `_ to create the locks::
267224
268225 use Symfony\Component\Lock\Store\SemaphoreStore;
269226
@@ -274,18 +231,11 @@ The SemaphoreStore uses the PHP semaphore functions to lock a ``resources``.
274231CombinedStore
275232~~~~~~~~~~~~~
276233
277- The CombinedStore synchronize several ``Stores `` together. When it's used to
278- acquired a Lock, it forwards the call to the managed ``Stores ``, and regarding
279- the result, uses a quorum to decide whether or not the lock is acquired.
280-
281- .. note ::
282-
283- This ``Store `` is useful for High availability application. You can provide
284- several Redis Server, and use theses server to manage the Lock. A
285- MajorityQuorum is enough to safely acquire a lock while it allow some Redis
286- server failure.
287-
288- .. code-block :: php
234+ The CombinedStore is designed for High Availability applications because it
235+ manages several stores in sync (for example, several Redis servers). When a lock
236+ is being acquired, it forwards the call to all the managed stores, and it
237+ collects their responses. If a simple majority of stores have acquired the lock,
238+ then the lock is considered as acquired; otherwise is not acquired::
289239
290240 use Symfony\Component\Lock\Quorum\MajorityQuorum;
291241 use Symfony\Component\Lock\Store\CombinedStore;
@@ -301,10 +251,9 @@ the result, uses a quorum to decide whether or not the lock is acquired.
301251
302252 $store = new CombinedStore($stores, new MajorityQuorum());
303253
304- .. tip ::
305-
306- You can use the CombinedStore with the UnanimousQuorum to implement chained
307- ``Stores ``. It'll allow you to acquire easy local locks before asking for a
308- remote lock
254+ Instead of the simple majority strategy (``MajorityQuorum ``) you can use the
255+ ``UnanimousQuorum `` to require the lock to be acquired in all the stores.
309256
257+ .. _`locks` : https://en.wikipedia.org/wiki/Lock_(computer_science)
310258.. _Packagist : https://packagist.org/packages/symfony/lock
259+ .. _`PHP semaphore functions` : http://php.net/manual/en/book.sem.php
0 commit comments