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

Skip to content
Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

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+
.. inde 8000 x::
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:r 10000 esource>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_s 49D1 ave.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)
0