Skip to content

Commit 8bf4f85

Browse files
jderussewouterj
authored andcommitted
Add a documentation page for lock in FW
1 parent c62f542 commit 8bf4f85

File tree

4 files changed

+303
-58
lines changed

4 files changed

+303
-58
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

index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Topics
4343
frontend
4444
http_cache
4545
http_client
46+
lock
4647
logging
4748
mailer
4849
mercure

lock.rst

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
.. index::
2+
single: Lock
3+
4+
Dealing with Concurrency with Locks
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 component </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 the Lock component:
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 :ref:`Semaphore <lock-store-semaphore>`
39+
when available, or a :ref:`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. Inside the same process, when several services share the
185+
same instance of ``LockInterface``, they won't lock each other. When the
186+
same process run concurent tasks, inject the ``LockFactory`` instead.
187+
188+
Locking a Dynamic Resource
189+
--------------------------
190+
191+
Sometimes the application is able to cut the resource into small pieces in order
192+
to lock a small subset of process and let other through. In our previous example
193+
with see how to lock the ``$pdf->getOrCreatePdf('terms-of-use')`` for everybody,
194+
now let's see how to lock ``$pdf->getOrCreatePdf($version)`` only for
195+
processes asking for the same ``$version``::
196+
197+
// src/Controller/PdfController.php
198+
namespace App\Controller;
199+
200+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
201+
use Symfony\Component\Lock\LockInterface;
202+
203+
class PdfController extends AbstractController
204+
{
205+
/**
206+
* @Route("/download/{version}/terms-of-use.pdf")
207+
*/
208+
public function downloadPdf($version, LockFactory $lockFactory, MyPdfGeneratorService $pdf)
209+
{
210+
$lock = $lockFactory->createLock($version);
211+
$lock->acquire(true);
212+
213+
// heavy computation
214+
$myPdf = $pdf->getOrCreatePdf($version);
215+
216+
$lock->release();
217+
218+
// ...
219+
}
220+
}
221+
222+
Named Lock
223+
----------
224+
225+
If the application needs different kind of Stores alongside each other, Symfony
226+
provides :ref:`named lock <reference-lock-resources-name>`::
227+
228+
.. configuration-block::
229+
230+
.. code-block:: yaml
231+
232+
# config/packages/lock.yaml
233+
framework:
234+
lock:
235+
invoice: ['semaphore', 'redis://r2.docker']
236+
report: 'semaphore'
237+
238+
.. code-block:: xml
239+
240+
<!-- config/packages/lock.xml -->
241+
<?xml version="1.0" encoding="UTF-8" ?>
242+
<container xmlns="http://symfony.com/schema/dic/services"
243+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
244+
xmlns:framework="http://symfony.com/schema/dic/symfony"
245+
xsi:schemaLocation="http://symfony.com/schema/dic/services
246+
https://symfony.com/schema/dic/services/services-1.0.xsd
247+
http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
248+
249+
<framework:config>
250+
<framework:lock>
251+
<framework:resource name="invoice">semaphore</framework:resource>
252+
<framework:resource name="invoice">redis://r2.docker</framework:resource>
253+
<framework:resource name="report">semaphore</framework:resource>
254+
</framework:lock>
255+
</framework:config>
256+
</container>
257+
258+
.. code-block:: php
259+
260+
// config/packages/lock.php
261+
$container->loadFromExtension('framework', [
262+
'lock' => [
263+
'invoice' => ['semaphore', 'redis://r2.docker'],
264+
'report' => 'semaphore',
265+
],
266+
]);
267+
268+
Each name becomes a service where the service id suffixed by the name of the
269+
lock (e.g. ``lock.invoice``). An autowiring alias is also created for each lock
270+
using the camel case version of its name suffixed by ``Lock`` - e.g. ``invoice``
271+
can be injected automatically by naming the argument ``$invoiceLock`` and
272+
type-hinting it with :class:`Symfony\\Component\\Lock\\LockInterface`.
273+
274+
Symfony also provide a corresponding factory and store following the same rules
275+
(e.g. ``invoice`` generates a ``lock.invoice.factory`` and
276+
``lock.invoice.store``, both can be injected automatically by naming
277+
respectively ``$invoiceLockFactory`` and ``$invoiceLockStore`` and type-hinted
278+
with :class:`Symfony\\Component\\Lock\\LockFactory` and
279+
:class:`Symfony\\Component\\Lock\\PersistingStoreInterface`)
280+
281+
Blocking Store
282+
--------------
283+
284+
If you want to use the ``RetryTillSaveStore`` for :ref:`non-blocking locks <lock-blocking-locks>`,
285+
you can do it by :doc:`decorating the store </service_container/service_decoration>` service:
286+
287+
.. code-block:: yaml
288+
289+
lock.default.retry_till_save.store:
290+
class: Symfony\Component\Lock\Store\RetryTillSaveStore
291+
decorates: lock.default.store
292+
arguments: ['@lock.default.retry_till_save.store.inner', 100, 50]

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy