Skip to content

Commit eda4f96

Browse files
committed
Merge branch '4.4' into 5.1
* 4.4: [symfony#11009] Some tweaks [DependencyInjection] Doc for #30257 Allow to choose an index for tagged collection [symfony#12523] Reworded caution Add a documentation page for lock in FW [Configuration] Add documentation about `ignore_errors: not_found` option.
2 parents 6488694 + 4ffbc62 commit eda4f96

File tree

7 files changed

+506
-67
lines changed

7 files changed

+506
-67
lines changed

Diff for: components/lock.rst

+3
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

Diff for: configuration.rst

+22-6
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,15 @@ configuration files, even if they use a different format:
8888
# config/services.yaml
8989
imports:
9090
- { resource: 'legacy_config.php' }
91-
# ignore_errors silently discards errors if the loaded file doesn't exist
92-
- { resource: 'my_config_file.xml', ignore_errors: true }
91+
9392
# glob expressions are also supported to load multiple files
9493
- { resource: '/etc/myapp/*.yaml' }
9594
95+
# ignore_errors: not_found silently discards errors if the loaded file doesn't exist
96+
- { resource: 'my_config_file.xml', ignore_errors: not_found }
97+
# ignore_errors: true silently discards all errors (including invalid code and not found)
98+
- { resource: 'my_other_config_file.xml', ignore_errors: true }
99+
96100
# ...
97101
98102
.. code-block:: xml
@@ -108,10 +112,13 @@ configuration files, even if they use a different format:
108112
109113
<imports>
110114
<import resource="legacy_config.php"/>
111-
<!-- ignore_errors silently discards errors if the loaded file doesn't exist -->
112-
<import resource="my_config_file.yaml" ignore-errors="true"/>
113115
<!-- glob expressions are also supported to load multiple files -->
114116
<import resource="/etc/myapp/*.yaml"/>
117+
118+
<!-- ignore-errors="not_found" silently discards errors if the loaded file doesn't exist -->
119+
<import resource="my_config_file.yaml" ignore-errors="not_found"/>
120+
<!-- ignore-errors="true" silently discards all errors (including invalid code and not found) -->
121+
<import resource="my_other_config_file.yaml" ignore-errors="true"/>
115122
</imports>
116123
117124
<!-- ... -->
@@ -124,14 +131,23 @@ configuration files, even if they use a different format:
124131
125132
return static function (ContainerConfigurator $container) {
126133
$container->import('legacy_config.php');
127-
// ignore_errors (3rd parameter) silently discards errors if the loaded file doesn't exist
128-
$container->import('my_config_file.xml', null, true);
134+
129135
// glob expressions are also supported to load multiple files
130136
$container->import('/etc/myapp/*.yaml');
137+
138+
// the third optional argument of import() is 'ignore_errors'
139+
// 'ignore_errors' set to 'not_found' silently discards errors if the loaded file doesn't exist
140+
$container->import('my_config_file.yaml', null, 'not_found');
141+
// 'ignore_errors' set to true silently discards all errors (including invalid code and not found)
142+
$container->import('my_config_file.yaml', null, true);
131143
};
132144
133145
// ...
134146
147+
.. versionadded:: 4.4
148+
149+
The ``not_found`` option value for ``ignore_errors`` was introduced in Symfony 4.4.
150+
135151
.. _config-parameter-intro:
136152
.. _config-parameters-yml:
137153
.. _configuration-parameters:

Diff for: index.rst

+1
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

Diff for: lock.rst

+292
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 use the
185+
same lock, inject the ``LockFactory`` instead to create a separate lock
186+
instance for each service.
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)