From 8bf4f85422fecf30c1c92f4ec95c0b7a5d2ab8cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Mon, 21 Oct 2019 14:51:50 +0200 Subject: [PATCH] Add a documentation page for lock in FW --- components/lock.rst | 3 + index.rst | 1 + lock.rst | 292 ++++++++++++++++++++++++++ reference/configuration/framework.rst | 65 +----- 4 files changed, 303 insertions(+), 58 deletions(-) create mode 100644 lock.rst diff --git a/components/lock.rst b/components/lock.rst index 6a1fd3cca62..45237fed221 100644 --- a/components/lock.rst +++ b/components/lock.rst @@ -8,6 +8,9 @@ The Lock Component The Lock Component creates and manages `locks`_, a mechanism to provide exclusive access to a shared resource. +If you're using the Symfony Framework, read the +:doc:`Symfony Framework Lock documentation `. + Installation ------------ diff --git a/index.rst b/index.rst index 07bc1a5ee6e..a1cd8e9f3c6 100644 --- a/index.rst +++ b/index.rst @@ -43,6 +43,7 @@ Topics frontend http_cache http_client + lock logging mailer mercure diff --git a/lock.rst b/lock.rst new file mode 100644 index 00000000000..aed72c9edf3 --- /dev/null +++ b/lock.rst @@ -0,0 +1,292 @@ +.. index:: + single: Lock + +Dealing with Concurrency with Locks +=================================== + +When a program runs concurrently, some part of code which modify shared +resources should not be accessed by multiple processes at the same time. +Symfony's :doc:`Lock component ` provides a locking mechanism to ensure +that only one process is running the critical section of code at any point of +time to prevent race condition from happening. + +The following example shows a typical usage of the lock:: + + $lock = $lockFactory->createLock('pdf-invoice-generation'); + if (!$lock->acquire()) { + return; + } + + // critical section of code + $service->method(); + + $lock->release(); + +Installation +------------ + +In applications using :ref:`Symfony Flex `, run this command to +install the Lock component: + +.. code-block:: terminal + + $ composer require symfony/lock + +Configuring Lock with FrameworkBundle +------------------------------------- + +By default, Symfony provides a :ref:`Semaphore ` +when available, or a :ref:`Flock ` otherwise. You can configure +this behavior by using the ``lock`` key like: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/lock.yaml + framework: + lock: ~ + lock: 'flock' + lock: 'flock:///path/to/file' + lock: 'semaphore' + lock: 'memcached://m1.docker' + lock: ['memcached://m1.docker', 'memcached://m2.docker'] + lock: 'redis://r1.docker' + lock: ['redis://r1.docker', 'redis://r2.docker'] + lock: 'zookeeper://z1.docker' + lock: 'zookeeper://z1.docker,z2.docker' + lock: 'sqlite:///%kernel.project_dir%/var/lock.db' + lock: 'mysql:host=127.0.0.1;dbname=lock' + lock: 'pgsql:host=127.0.0.1;dbname=lock' + lock: 'sqlsrv:server=localhost;Database=test' + lock: 'oci:host=localhost;dbname=test' + lock: '%env(LOCK_DSN)%' + + # named locks + lock: + invoice: ['semaphore', 'redis://r2.docker'] + report: 'semaphore' + + .. code-block:: xml + + + + + + + + flock + + flock:///path/to/file + + semaphore + + memcached://m1.docker + + memcached://m1.docker + memcached://m2.docker + + redis://r1.docker + + redis://r1.docker + redis://r2.docker + + zookeeper://z1.docker + + zookeeper://z1.docker,z2.docker + + sqlite:///%kernel.project_dir%/var/lock.db + + mysql:host=127.0.0.1;dbname=lock + + pgsql:host=127.0.0.1;dbname=lock + + sqlsrv:server=localhost;Database=test + + oci:host=localhost;dbname=test + + %env(LOCK_DSN)% + + + semaphore + redis://r2.docker + semaphore + + + + + .. code-block:: php + + // config/packages/lock.php + $container->loadFromExtension('framework', [ + 'lock' => null, + 'lock' => 'flock', + 'lock' => 'flock:///path/to/file', + 'lock' => 'semaphore', + 'lock' => 'memcached://m1.docker', + 'lock' => ['memcached://m1.docker', 'memcached://m2.docker'], + 'lock' => 'redis://r1.docker', + 'lock' => ['redis://r1.docker', 'redis://r2.docker'], + 'lock' => 'zookeeper://z1.docker', + 'lock' => 'zookeeper://z1.docker,z2.docker', + 'lock' => 'sqlite:///%kernel.project_dir%/var/lock.db', + 'lock' => 'mysql:host=127.0.0.1;dbname=lock', + 'lock' => 'pgsql:host=127.0.0.1;dbname=lock', + 'lock' => 'sqlsrv:server=localhost;Database=test', + 'lock' => 'oci:host=localhost;dbname=test', + 'lock' => '%env(LOCK_DSN)%', + + // named locks + 'lock' => [ + 'invoice' => ['semaphore', 'redis://r2.docker'], + 'report' => 'semaphore', + ], + ]); + +Locking a Resource +------------------ + +To lock the default resource, autowire the lock using +:class:`Symfony\\Component\\Lock\\LockInterface` (service id ``lock``):: + + // src/Controller/PdfController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Lock\LockInterface; + + class PdfController extends AbstractController + { + /** + * @Route("/download/terms-of-use.pdf") + */ + public function downloadPdf(LockInterface $lock, MyPdfGeneratorService $pdf) + { + $lock->acquire(true); + + // heavy computation + $myPdf = $pdf->getOrCreatePdf(); + + $lock->release(); + + // ... + } + } + +.. caution:: + + The same instance of ``LockInterface`` won't block when calling ``acquire`` + multiple times. Inside the same process, when several services share the + same instance of ``LockInterface``, they won't lock each other. When the + same process run concurent tasks, inject the ``LockFactory`` instead. + +Locking a Dynamic Resource +-------------------------- + +Sometimes the application is able to cut the resource into small pieces in order +to lock a small subset of process and let other through. In our previous example +with see how to lock the ``$pdf->getOrCreatePdf('terms-of-use')`` for everybody, +now let's see how to lock ``$pdf->getOrCreatePdf($version)`` only for +processes asking for the same ``$version``:: + + // src/Controller/PdfController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Lock\LockInterface; + + class PdfController extends AbstractController + { + /** + * @Route("/download/{version}/terms-of-use.pdf") + */ + public function downloadPdf($version, LockFactory $lockFactory, MyPdfGeneratorService $pdf) + { + $lock = $lockFactory->createLock($version); + $lock->acquire(true); + + // heavy computation + $myPdf = $pdf->getOrCreatePdf($version); + + $lock->release(); + + // ... + } + } + +Named Lock +---------- + +If the application needs different kind of Stores alongside each other, Symfony +provides :ref:`named lock `:: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/lock.yaml + framework: + lock: + invoice: ['semaphore', 'redis://r2.docker'] + report: 'semaphore' + + .. code-block:: xml + + + + + + + + semaphore + redis://r2.docker + semaphore + + + + + .. code-block:: php + + // config/packages/lock.php + $container->loadFromExtension('framework', [ + 'lock' => [ + 'invoice' => ['semaphore', 'redis://r2.docker'], + 'report' => 'semaphore', + ], + ]); + +Each name becomes a service where the service id suffixed by the name of the +lock (e.g. ``lock.invoice``). An autowiring alias is also created for each lock +using the camel case version of its name suffixed by ``Lock`` - e.g. ``invoice`` +can be injected automatically by naming the argument ``$invoiceLock`` and +type-hinting it with :class:`Symfony\\Component\\Lock\\LockInterface`. + +Symfony also provide a corresponding factory and store following the same rules +(e.g. ``invoice`` generates a ``lock.invoice.factory`` and +``lock.invoice.store``, both can be injected automatically by naming +respectively ``$invoiceLockFactory`` and ``$invoiceLockStore`` and type-hinted +with :class:`Symfony\\Component\\Lock\\LockFactory` and +:class:`Symfony\\Component\\Lock\\PersistingStoreInterface`) + +Blocking Store +-------------- + +If you want to use the ``RetryTillSaveStore`` for :ref:`non-blocking locks `, +you can do it by :doc:`decorating the store ` service: + +.. code-block:: yaml + + lock.default.retry_till_save.store: + class: Symfony\Component\Lock\Store\RetryTillSaveStore + decorates: lock.default.store + arguments: ['@lock.default.retry_till_save.store.inner', 100, 50] diff --git a/reference/configuration/framework.rst b/reference/configuration/framework.rst index efcdb5d16c5..62d68a5b929 100644 --- a/reference/configuration/framework.rst +++ b/reference/configuration/framework.rst @@ -1346,8 +1346,8 @@ The possible values for this option are: * ``null``, use it to disable this protection. Same behavior as in older Symfony versions. -* ``'none'`` (or the ``Cookie::SAMESITE_NONE`` constant), use it to allow - sending of cookies when the HTTP request originated from a different domain +* ``'none'`` (or the ``Cookie::SAMESITE_NONE`` constant), use it to allow + sending of cookies when the HTTP request originated from a different domain (previously this was the default behavior of null, but in newer browsers ``'lax'`` would be applied when the header has not been set) * ``'strict'`` (or the ``Cookie::SAMESITE_STRICT`` constant), use it to never @@ -2836,21 +2836,7 @@ A list of lock stores to be created by the framework extension. # config/packages/lock.yaml framework: - # these are all the supported lock stores - lock: ~ - lock: 'flock' - lock: 'flock:///path/to/file' - lock: 'semaphore' - lock: 'memcached://m1.docker' - lock: ['memcached://m1.docker', 'memcached://m2.docker'] - lock: 'redis://r1.docker' - lock: ['redis://r1.docker', 'redis://r2.docker'] - lock: '%env(MEMCACHED_OR_REDIS_URL)%' - - # named locks - lock: - invoice: ['redis://r1.docker', 'redis://r2.docker'] - report: 'semaphore' + lock: '%env(LOCK_DSN)%' .. code-block:: xml @@ -2865,29 +2851,7 @@ A list of lock stores to be created by the framework extension. - - flock - - flock:///path/to/file - - semaphore - - memcached://m1.docker - - memcached://m1.docker - memcached://m2.docker - - redis://r1.docker - - redis://r1.docker - redis://r2.docker - - %env(REDIS_URL)% - - - redis://r1.docker - redis://r2.docker - semaphore + %env(LOCK_DSN)% @@ -2896,27 +2860,12 @@ A list of lock stores to be created by the framework extension. // config/packages/lock.php $container->loadFromExtension('framework', [ - // these are all the supported lock stores - 'lock' => null, - 'lock' => 'flock', - 'lock' => 'flock:///path/to/file', - 'lock' => 'semaphore', - 'lock' => 'memcached://m1.docker', - 'lock' => ['memcached://m1.docker', 'memcached://m2.docker'], - 'lock' => 'redis://r1.docker', - 'lock' => ['redis://r1.docker', 'redis://r2.docker'], - 'lock' => '%env(MEMCACHED_OR_REDIS_URL)%', - - // named locks - 'lock' => [ - 'invoice' => ['redis://r1.docker', 'redis://r2.docker'], - 'report' => 'semaphore', - ], + 'lock' => '%env(LOCK_DSN)%', ]); -.. versionadded:: 4.2 +.. seealso:: - The ``flock://`` store was introduced in Symfony 4.2. + For more details, see :doc:`/lock`. .. _reference-lock-resources-name: