8000 Add a documentation page for lock in FW · symfony/symfony-docs@f4d1ed9 · GitHub
[go: up one dir, main page]

Skip to content

Commit f4d1ed9

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

File tree

3 files changed

+301
-65
lines changed

3 files changed

+301
-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: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
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+
lock: ~
49+
lock: 'flock'
50+
lock: 'flock:///path/to/file'
51+
lock: 'semaphore'
52+
lock: 'memcached://m1.docker'
53+
lock: ['memcached://m1.docker', 'memcached://m2.docker']
54+
lock: 'redis://r1.docker'
55+
lock: ['redis://r1.docker', 'redis://r2.docker']
56+
lock: 'zookeeper://z1.docker'
57+
lock: 'zookeeper://z1.docker,z2.docker'
58+
lock: 'sqlite:///%kernel.project_dir%/var/lock.db'
59+
lock: 'mysql:host=127.0.0.1;dbname=lock'
60+
lock: 'pgsql:host=127.0.0.1;dbname=lock'
61+
lock: 'sqlsrv:server=localhost;Database=test'
62+
lock: 'oci:host=localhost;dbname=test'
63+
lock: '%env(LOCK_DSN)%'
64+
65+
# named locks
66+
lock:
67+
invoice: ['semaphore', 'redis://r2.docker']
68+
report: 'semaphore'
69+
70+
.. code-block:: xml
71+
72+
<!-- config/packages/lock.xml -->
73+
<?xml version="1.0" encoding="UTF-8" ?>
74+
<container xmlns="http://symfony.com/schema/dic/services"
75+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
76+
xmlns:framework="http://symfony.com/schema/dic/symfony"
77+
xsi:schemaLocation="http://symfony.com/schema/dic/services
78+
https://symfony.com/schema/dic/services/services-1.0.xsd
79+
http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
80+
81+
<framework:config>
82+
<framework:lock>
83+
<framework:resource>flock</framework:resource>
84+
85+
<framework:resource>flock:///path/to/file</framework:resource>
86+
87+
<framework:resource>semaphore</framework:resource>
88+
89+
<framework:resource>memcached://m1.docker</framework:resource>
90+
91+
<framework:resource>memcached://m1.docker</framework:resource>
92+
<framework:resource>memcached://m2.docker</framework:resource>
93+
94+
<framework:resource>redis://r1.docker</framework:resource>
95+
96+
<framework:resource>redis://r1.docker</framework:resource>
97+
<framework:resource>redis://r2.docker</framework:resource>
98+
99+
<framework:resource>zookeeper://z1.docker</framework:resource>
100+
101+
<framework:resource>zookeeper://z1.docker,z2.docker</framework:resource>
102+
103+
<framework:resource>sqlite:///%kernel.project_dir%/var/lock.db</framework:resource>
104+
105+
<framework:resource>mysql:host=127.0.0.1;dbname=lock</framework:resource>
106+
107+
<framework:resource>pgsql:host=127.0.0.1;dbname=lock</framework:resource>
108+
109+
<framework:resource>sqlsrv:server=localhost;Database=test</framework:resource>
110+
111+
<framework:resource>oci:host=localhost;dbname=test</framework:resource>
112+
113+
<framework:resource>%env(LOCK_DSN)%</framework:resource>
114+
115+
<!-- named locks -->
116+
<framework:resource name="invoice">semaphore</framework:resource>
117+
<framework:resource name="invoice">redis://r2.docker</framework:resource>
118+
<framework:resource name="report">semaphore</framework:resource>
119+
</framework:lock>
120+
</framework:config>
121+
</container>
122+
123+
.. code-block:: php
124+
125+
// config/packages/lock.php
126+
$container->loadFromExtension('framework', [
127+
'lock' => null,
128+
'lock' => 'flock',
129+
'lock' => 'flock:///path/to/file',
130+
'lock' => 'semaphore',
131+
'lock' => 'memcached://m1.docker',
132+
'lock' => ['memcached://m1.docker', 'memcached://m2.docker'],
133+
'lock' => 'redis://r1.docker',
134+
'lock' => ['redis://r1.docker', 'redis://r2.docker'],
135+
'lock' => 'zookeeper://z1.docker',
136+
'lock' => 'zookeeper://z1.docker,z2.docker',
137+
'lock' => 'sqlite:///%kernel.project_dir%/var/lock.db',
138+
'lock' => 'mysql:host=127.0.0.1;dbname=lock',
139+
'lock' => 'pgsql:host=127.0.0.1;dbname=lock',
140+
'lock' => 'sqlsrv:server=localhost;Database=test',
141+
'lock' => 'oci:host=localhost;dbname=test',
142+
'lock' => '%env(LOCK_DSN)%',
143+
144+
// named locks
145+
'lock' => [
146+
'invoice' => ['semaphore', 'redis://r2.docker'],
147+
'report' => 'semaphore',
148+
],
149+
]);
150+
151+
Locking a Resource
152+
------------------
153+
154+
To lock the default resource, autowire the lock using
155+
:class:`Symfony\\Component\\Lock\\LockInterface` (service id ``lock``)::
156+
157+
// src/Controller/PdfController.php
158+
namespace App\Controller;
159+
160+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
161+
use Symfony\Component\Lock\LockInterface;
162+
163+
class PdfController extends AbstractController
164+
{
165+
/**
166+
* @Route("/download/terms-of-use.pdf")
167+
*/
168+
public function downloadPdf(LockInterface $lock, MyPdfGeneratorService $pdf)
169+
{
170+
$lock->acquire(true);
171+
172+
// heavy computation
173+
$myPdf = $pdf->getOrCreatePdf();
174+
175+
$lock->release();
176+
177+
// ...
178+
}
179+
}
180+
181+
.. caution::
182+
183+
The same instance of ``LockInterface`` won't block when calling `acquire``
184+
multiple times. If several services share the same ``lock`` service, they
185+
won't lock each other, use ``LockFactory`` instead.
186+
187+
Locking a Dynamic Resource
188+
--------------------------
189+
190+
Sometimes the application is able to cut the resource into small pieces in order
191+
to lock a small subset of process and let other through. In our previous example
192+
with see how to lock the ``$pdf->getOrCreatePdf('terms-of-use')`` for everybody,
193+
now let's see how to lock ``$pdf->getOrCreatePdf($version)`` only for
194+
processes asking for the same ``$version``::
195+
196+
// src/Controller/PdfController.php
197+
namespace App\Controller;
198+
199+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
200+
use Symfony\Component\Lock\LockInterface;
201+
202+
class PdfController extends AbstractController
203+
{
204+
/**
205+
* @Route("/download/{version}/terms-of-use.pdf")
206+
*/
207+
public function downloadPdf($version, LockFactory $lockFactory, MyPdfGeneratorService $pdf)
208+
{
209+
$lock = $lockFactory->createLock($version);
210+
$lock->acquire(true);
211+
212+
// heavy computation
213+
$myPdf = $pdf->getOrCreatePdf($version);
214+
215+
$lock->release();
216+
217+
// ...
218+
}
219+
}
220+
221+
Named Lock
222+
----------
223+
224+
If the application needs different kind of Stores alongside each other, Symfony
225+
provides :doc:`named lock <reference-lock-resources-name>`::
226+
227+
.. configuration-block::
228+
229+
.. code-block:: yaml
230+
231+
# config/packages/lock.yaml
232+
framework:
233+
lock:
234+
invoice: ['semaphore', 'redis://r2.docker']
235+
report: 'semaphore'
236+
237+
.. code-block:: xml
238+
239+
<!-- config/packages/lock.xml -->
240+
<?xml version="1.0" encoding="UTF-8" ?>
241+
<container xmlns="http://symfony.com/schema/dic/services"
242+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
243+
xmlns:framework="http://symfony.com/schema/dic/symfony"
244+
xsi:schemaLocation="http://symfony.com/schema/dic/services
245+
https://symfony.com/schema/dic/services/services-1.0.xsd
246+
http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
247+
248+
<framework:config>
249+
<framework:lock>
250+
<framework:resource name="invoice">semaphore</framework:resource>
251+
<framework:resource name="invoice">redis://r2.docker</framework:resource>
252+
<framework:resource name="report">semaphore</framework:resource>
253+
</framework:lock>
254+
</framework:config>
255+
</container>
256+
257+
.. code-block:: php
258+
259+
// config/packages/lock.php
260+
$container->loadFromExtension('framework', [
261+
'lock' => [
262+
'invoice' => ['semaphore', 'redis://r2.docker'],
263+
'report' => 'semaphore',
264+
],
265+
]);
266+
267+
Each name becomes a service where the service id suffixed by the name of the
268+
lock (e.g. ``lock.invoice``). An autowiring alias is also created for each lock
269+
using the camel case version of its name suffixed by ``Lock`` - e.g. ``invoice``
270+
can be injected automatically by naming the argument ``$invoiceLock`` and
271+
type-hinting it with :class:`Symfony\\Component\\Lock\\LockInterface`.
272+
273+
Symfony also provide a corresponding factory and store following the same rules
274+
(e.g. ``invoice`` generates a ``lock.invoice.factory`` and
275+
``lock.invoice.store``, both can be injected automatically by naming
276+
respectively ``$invoiceLockFactory`` and ``invoiceLockStore`` and type-hinted
277+
with :class:`Symfony\\Component\\Lock\\LockFactory` and
278+
:class:`Symfony\\Component\\Lock\\PersistingStoreInterface`)
279+
280+
Blocking Store
281+
--------------
282+
283+
If you want to use the ``RetryTillSaveStore`` for :ref:`non-blocking locks <lock-blocking-locks>`,
284+
you can do it by :doc:`decorating the store </service_container/service_decoration>` service:
285+
286+
.. code-block:: yaml
287+
288+
lock.default.retry_till_save.store:
289+
class: Symfony\Component\Lock\Store\RetryTillSaveStore
290+
decorates: lock.default.store
291+
arguments: ['@lock.default.retry.till.save.store.inner', 100, 50]

0 commit comments

Comments
 (0)
0