Skip to content

Commit ec93783

Browse files
committed
Add a documentation page for lock in FW
1 parent 1de98f9 commit ec93783

File tree

3 files changed

+304
-65
lines changed

3 files changed

+304
-65
lines changed

components/lock.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ The Lock Component
88
The Lock Component creates and manages `locks`_, a mechanism to provide
99
exclusive access to a shared resource.
1010

11+
If you're using the Symfony Framework, read the
12+
:doc:`Symfony Framework Lock documentation </lock>`.
13+
1114
Installation
1215
------------
1316

lock.rst

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
.. index::
2+
single: Lock
3+
4+
Dealing with concurrency with Lock
5+
==================================
6+
7+
When a program runs concurrently, some part of code which modify shared
8+
resources should not be accessed by multiple processes at the same time.
9+
Symfony's :doc:`Lock </components/lock>` provides a locking mechanism to ensure
10+
that only one process is running the critical section of code at any point of
11+
time to prevent race condition from happening.
12+
13+
The following example shows a typical usage of the lock::
14+
15+
$lock = $lockFactory->createLock('pdf-invoice-generation');
16+
if (!$lock->acquire()) {
17+
return;
18+
}
19+
20+
// critical section of code
21+
$service->method();
22+
23+
$lock->release();
24+
25+
Installation
26+
------------
27+
28+
In applications using :ref:`Symfony Flex <symfony-flex>`, run this command to
29+
install messenger:
30+
31+
.. code-block:: terminal
32+
33+
$ composer require symfony/lock
34+
35+
Configuring Lock with FrameworkBundle
36+
-------------------------------------
37+
38+
By default, Symfony provides a :doc:`Semaphore<lock-store-semaphore>`
39+
when available, or a :doc:`Flock<lock-store-flock>` otherwise. You can configure
40+
this behavior by using the ``lock`` key like:
41+
42+
.. configuration-block::
43+
44+
.. code-block:: yaml
45+
46+
# config/packages/lock.yaml
47+
framework:
48+
# these are all the supported lock stores
49+
lock: ~
50+
lock: 'flock'
51+
lock: 'flock:///path/to/file'
52+
lock: 'semaphore'
53+
lock: 'memcached://m1.docker'
54+
lock: ['memcached://m1.docker', 'memcached://m2.docker']
55+
lock: 'redis://r1.docker'
56+
lock: ['redis://r1.docker', 'redis://r2.docker']
57+
lock: 'zookeeper://z1.docker'
58+
lock: 'zookeeper://z1.docker,z2.docker'
59+
lock: 'sqlite:///%kernel.project_dir%/var/lock.db'
60+
lock: 'mysql:host=127.0.0.1;dbname=lock'
61+
lock: 'pgsql:host=127.0.0.1;dbname=lock'
62+
lock: 'sqlsrv:server=localhost;Database=test'
63+
lock: 'oci:host=localhost;dbname=test'
64+
lock: '%env(LOCK_DSN)%'
65+
66+
# named locks
67+
lock:
68+
invoice: ['semaphore', 'redis://r2.docker']
69+
report: 'semaphore'
70+
71+
.. code-block:: xml
72+
73+
<!-- config/packages/lock.xml -->
74+
<?xml version="1.0" encoding="UTF-8" ?>
75+
<container xmlns="http://symfony.com/schema/dic/services"
76+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
77+
xmlns:framework="http://symfony.com/schema/dic/symfony"
78+
xsi:schemaLocation="http://symfony.com/schema/dic/services
79+
https://symfony.com/schema/dic/services/services-1.0.xsd
80+
http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
81+
82+
<framework:config>
83+
<framework:lock>
84+
<!-- these are all the supported lock stores -->
85+
<framework:resource>flock</framework:resource>
86+
87+
<framework:resource>flock:///path/to/file</framework:resource>
88+
89+
<framework:resource>semaphore</framework:resource>
90+
91+
<framework:resource>memcached://m1.docker</framework:resource>
92+
93+
<framework:resource>memcached://m1.docker</framework:resource>
94+
<framework:resource>memcached://m2.docker</framework:resource>
95+
96+
<framework:resource>redis://r1.docker</framework:resource>
97+
98+
<framework:resource>redis://r1.docker</framework:resource>
99+
<framework:resource>redis://r2.docker</framework:resource>
100+
101+
<framework:resource>zookeeper://z1.docker</framework:resource>
102+
103+
<framework:resource>zookeeper://z1.docker,z2.docker</framework:resource>
104+
105+
<framework:resource>sqlite:///%kernel.project_dir%/var/lock.db</framework:resource>
106+
107+
<framework:resource>mysql:host=127.0.0.1;dbname=lock</framework:resource>
108+
109+
<framework:resource>pgsql:host=127.0.0.1;dbname=lock</framework:resource>
110+
111+
<framework:resource>sqlsrv:server=localhost;Database=test</framework:resource>
112+
113+
<framework:resource>oci:host=localhost;dbname=test</framework:resource>
114+
115+
<framework:resource>%env(LOCK_DSN)%</framework:resource>
116+
117+
<!-- named locks -->
118+
<framework:resource name="invoice">semaphore</framework:resource>
119+
<framework:resource name="invoice">redis://r2.docker</framework:resource>
120+
<framework:resource name="report">semaphore</framework:resource>
121+
</framework:lock>
122+
</framework:config>
123+
</container>
124+
125+
.. code-block:: php
126+
127+
// config/packages/lock.php
128+
$container->loadFromExtension('framework', [
129+
// these are all the supported lock stores
130+
'lock' => null,
131+
'lock' => 'flock',
132+
'lock' => 'flock:///path/to/file',
133+
'lock' => 'semaphore',
134+
'lock' => 'memcached://m1.docker',
135+
'lock' => ['memcached://m1.docker', 'memcached://m2.docker'],
136+
'lock' => 'redis://r1.docker',
137+
'lock' => ['redis://r1.docker', 'redis://r2.docker'],
138+
'lock' => 'zookeeper://z1.docker',
139+
'lock' => 'zookeeper://z1.docker,z2.docker',
140+
'lock' => 'sqlite:///%kernel.project_dir%/var/lock.db',
141+
'lock' => 'mysql:host=127.0.0.1;dbname=lock',
142+
'lock' => 'pgsql:host=127.0.0.1;dbname=lock',
143+
'lock' => 'sqlsrv:server=localhost;Database=test',
144+
'lock' => 'oci:host=localhost;dbname=test',
145+
'lock' => '%env(LOCK_DSN)%',
146+
147+
// named locks
148+
'lock' => [
149+
'invoice' => ['semaphore', 'redis://r2.docker'],
150+
'report' => 'semaphore',
151+
],
152+
]);
153+
154+
Locking a resource
155+
------------------
156+
157+
To lock the default resource, autowire the lock using
158+
:class:`Symfony\\Component\\Lock\\LockInterface` (service id ``lock``)::
159+
160+
// src/Controller/PdfController.php
161+
namespace App\Controller;
162+
163+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
164+
use Symfony\Component\Lock\LockInterface;
165+
166+
class PdfController extends AbstractController
167+
{
168+
/**
169+
* @Route("/download/term-of-use.pdf")
170+
*/
171+
public function downloadPdf(LockInterface $lock, MyPdfGeneratorService $pdf)
172+
{
173+
$lock->acquire(true);
174+
175+
// heavy computation
176+
$myPdf = $pdf->getOrCreatePdf();
177+
178+
$lock->release();
179+
180+
// ...
181+
}
182+
}
183+
184+
.. caution::
185+
186+
The same instance of ``LockInterface`` won't block when calling `acquire``
187+
multiple times. If several services share the same ``lock`` service, they
188+
won't lock each other, use ``LockFactory`` instead.
189+
190+
Locking a dynamic resource
191+
--------------------------
192+
193+
Sometimes the application is able to cut the resource into small pieces in order
194+
to lock a small subset of process and let other through. In our previous example
195+
with see how to lock the ``$pdf->getOrCreatePdf('term-of-use')`` for everybody,
196+
now let's see how to lock ``$pdf->getOrCreatePdf($version)`` only for
197+
processes asking for the same ``$version``::
198+
199+
// src/Controller/PdfController.php
200+
namespace App\Controller;
201+
202+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
203+
use Symfony\Component\Lock\LockInterface;
204+
205+
class PdfController extends AbstractController
206+
{
207+
/**
208+
* @Route("/download/${version}/term-of-use.pdf")
209+
*/
210+
public function downloadPdf($version, LockFactory $lockFactory, MyPdfGeneratorService $pdf)
211+
{
212+
$lock = $lockFactory->createLock($version);
213+
$lock->acquire(true);
214+
215+
// heavy computation
216+
$myPdf = $pdf->getOrCreatePdf($version);
217+
218+
$lock->release();
219+
220+
// ...
221+
}
222+
}
223+
224+
Named lock
225+
----------
226+
227+
If the application needs different kind of Stores alongside each other, Symfony
228+
provides :doc:`named lock <reference-lock-resources-name>`::
229+
230+
.. configuration-block::
231+
232+
.. code-block:: yaml
233+
234+
# config/packages/lock.yaml
235+
framework:
236+
lock:
237+
invoice: ['semaphore', 'redis://r2.docker']
238+
report: 'semaphore'
239+
240+
.. code-block:: xml
241+
242+
<!-- config/packages/lock.xml -->
243+
<?xml version="1.0" encoding="UTF-8" ?>
244+
<container xmlns="http://symfony.com/schema/dic/services"
245+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
246+
xmlns:framework="http://symfony.com/schema/dic/symfony"
247+
xsi:schemaLocation="http://symfony.com/schema/dic/services
248+
https://symfony.com/schema/dic/services/services-1.0.xsd
249+
http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
250+
251+
<framework:config>
252+
<framework:lock>
253+
<framework:resource name="invoice">semaphore</framework:resource>
254+
<framework:resource name="invoice">redis://r2.docker</framework:resource>
255+
<framework:resource name="report">semaphore</framework:resource>
256+
</framework:lock>
257+
</framework:config>
258+
</container>
259+
260+
.. code-block:: php
261+
262+
// config/packages/lock.php
263+
$container->loadFromExtension('framework', [
264+
'lock' => [
265+
'invoice' => ['semaphore', 'redis://r2.docker'],
266+
'report' => 'semaphore',
267+
],
268+
]);
269+
270+
Each name becomes a service where the service id suffixed by the name of the
271+
lock (e.g. ``lock.invoice``). An autowiring alias is also created for each lock
272+
using the camel case version of its name suffixed by ``Lock`` - e.g. ``invoice``
273+
can be injected automatically by naming the argument ``$invoiceLock`` and
274+
type-hinting it with :class:`Symfony\\Component\\Lock\\LockInterface`.
275+
276+
Symfony also provide a corresponding factory and store following the same rules
277+
(e.g. ``invoice`` generates a ``lock.invoice.factory`` and
278+
``lock.invoice.store``, both can be injected automatically by naming
279+
respectively ``$invoiceLockFactory`` and ``invoiceLockStore`` and type-hinted
280+
with :class:`Symfony\\Component\\Lock\\LockFactory` and
281+
:class:`Symfony\\Component\\Lock\\PersistingStoreInterface`)
282+
283+
Blocking store
284+
--------------
285+
286+
If you want to use the `RetryTillSaveStore` for :ref:`non-blocking locks <lock-blocking-locks>`,
287+
you can do it by :doc:`decorating the store </service_container/service_decoration>` service:
288+
289+
.. code-block:: yaml
290+
291+
lock.default.retry_till_save.store:
292+
class: Symfony\Component\Lock\Store\RetryTillSaveStore
293+
decorates: lock.default.store
294+
arguments: ['@lock.default.retry.till.save.store.inner', 100, 50]

0 commit comments

Comments
 (0)