5
5
The Lock Component
6
6
==================
7
7
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.
11
10
12
11
.. versionadded :: 3.3
13
12
The Lock component was introduced in Symfony 3.3.
@@ -25,134 +24,118 @@ You can install the component in 2 different ways:
25
24
Usage
26
25
-----
27
26
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) .
31
30
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::
38
34
39
35
use Symfony\Component\Lock\Factory;
40
36
use Symfony\Component\Lock\Store\SemaphoreStore;
41
37
42
38
$store = new SemaphoreStore();
43
39
$factory = new Factory($store);
44
- $lock = $factory->createLock('invoice-pdf-generation');
45
40
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');
48
47
48
+ if ($lock->acquire()) {
49
+ // The resource "pdf-invoice-generation" is locked.
49
50
// You can compute and generate invoice safely here.
50
51
51
52
$lock->release();
52
53
}
53
54
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 .
56
57
57
58
.. note ::
58
59
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.
64
64
65
65
Blocking Locks
66
66
--------------
67
67
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.
75
73
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::
77
76
78
77
use Symfony\Component\Lock\Factory;
79
78
use Symfony\Component\Lock\Store\RedisStore;
80
79
use Symfony\Component\Lock\Store\RetryTillSaveStore;
81
80
82
81
$store = new RedisStore(new \Predis\Client('tcp://localhost:6379'));
83
82
$store = new RetryTillSaveStore($store);
84
-
85
83
$factory = new Factory($store);
86
84
87
85
$lock = $factory->createLock('notification-flush');
88
-
89
86
$lock->acquire(true);
90
87
91
88
Expiring Locks
92
89
--------------
93
90
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.
99
95
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.
104
101
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::
115
106
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
119
109
$lock = $factory->createLock('charts-generation', 30);
120
110
121
111
$lock->acquire();
122
112
try {
123
- // perfom a job during less than 30 seconds
113
+ // perform a job during less than 30 seconds
124
114
} finally {
125
115
$lock->release();
126
116
}
127
117
128
118
.. tip ::
129
119
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 .
132
122
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::
138
126
139
127
.. code-block :: php
140
128
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
+ // ...
147
130
$lock = $factory->createLock('charts-generation', 30);
148
131
149
132
$lock->acquire();
150
133
try {
151
134
while (!$finished) {
152
135
// perform a small part of the job.
153
136
137
+ // renew the lock for 30 more seconds.
154
138
$lock->refresh();
155
- // resource is locked for 30 more seconds.
156
139
}
157
140
} finally {
158
141
$lock->release();
@@ -161,71 +144,48 @@ regularly refresh the lock.
161
144
Available Stores
162
145
----------------
163
146
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:
168
150
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
- +----------------------------------------------+--------+----------+----------+
180
151
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
+ ============================================ ====== ======== ========
186
160
187
161
.. _lock-store-flock :
188
162
189
163
FlockStore
190
164
~~~~~~~~~~
191
165
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::
197
169
198
170
use Symfony\Component\Lock\Store\FlockStore;
199
171
172
+ // the argument is the path of the directory where the locks are created
200
173
$store = new FlockStore(sys_get_temp_dir());
201
174
202
- The first argument of the constructor is the path to the directory where the
203
- file will be created.
204
-
205
175
.. caution ::
206
176
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.
212
180
213
181
.. _lock-store-memcached :
214
182
215
183
MemcachedStore
216
184
~~~~~~~~~~~~~~
217
185
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::
229
189
230
190
use Symfony\Component\Lock\Store\MemcachedStore;
231
191
@@ -234,20 +194,19 @@ implements the ``\Memcached`` class.
234
194
235
195
$store = new MemcachedStore($memcached);
236
196
197
+ .. note ::
198
+
199
+ Memcached does not support TTL lower than 1 second.
200
+
237
201
.. _lock-store-redis :
238
202
239
203
RedisStore
240
204
~~~~~~~~~~
241
205
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::
251
210
252
211
use Symfony\Component\Lock\Store\RedisStore;
253
212
@@ -261,9 +220,7 @@ classes
261
220
SemaphoreStore
262
221
~~~~~~~~~~~~~~
263
222
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::
267
224
268
225
use Symfony\Component\Lock\Store\SemaphoreStore;
269
226
@@ -274,18 +231,11 @@ The SemaphoreStore uses the PHP semaphore functions to lock a ``resources``.
274
231
CombinedStore
275
232
~~~~~~~~~~~~~
276
233
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::
289
239
290
240
use Symfony\Component\Lock\Quorum\MajorityQuorum;
291
241
use Symfony\Component\Lock\Store\CombinedStore;
@@ -301,10 +251,9 @@ the result, uses a quorum to decide whether or not the lock is acquired.
301
251
302
252
$store = new CombinedStore($stores, new MajorityQuorum());
303
253
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.
309
256
257
+ .. _`locks` : https://en.wikipedia.org/wiki/Lock_(computer_science)
310
258
.. _Packagist : https://packagist.org/packages/symfony/lock
259
+ .. _`PHP semaphore functions` : http://php.net/manual/en/book.sem.php
0 commit comments