Symfony
sponsored by SensioLabs
Menu
  • About
  • Documentation
  • Screencasts
  • Cloud
  • Certification
  • Community
  • Businesses
  • News
  • Download
  1. Home
  2. Documentation
  3. Dealing with Concurrency with Locks
  • Documentation
  • Book
  • Reference
  • Bundles
  • Cloud
Search by Algolia

Table of Contents

  • Installing
  • Configuring
  • Locking a Resource
  • Locking a Dynamic Resource
  • Naming Locks

Dealing with Concurrency with Locks

Edit this page

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 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 conditions from happening.

The following example shows a typical usage of the lock:

1
2
3
4
5
6
7
8
9
$lock = $lockFactory->createLock('pdf-creation');
if (!$lock->acquire()) {
    return;
}

// critical section of code
$service->method();

$lock->release();

Installing

In applications using Symfony Flex, run this command to install the Lock component:

1
$ composer require symfony/lock

Configuring

By default, Symfony provides a Semaphore when available, or a Flock otherwise. You can configure this behavior by using the lock key like:

  • YAML
  • XML
  • PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 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: 'zookeeper://localhost01,localhost02:2181'
    lock: 'sqlite:///%kernel.project_dir%/var/lock.db'
    lock: 'mysql:host=127.0.0.1;dbname=app'
    lock: 'pgsql:host=127.0.0.1;dbname=app'
    lock: 'pgsql+advisory:host=127.0.0.1;dbname=app'
    lock: 'sqlsrv:server=127.0.0.1;Database=app'
    lock: 'oci:host=127.0.0.1;dbname=app'
    lock: 'mongodb://127.0.0.1/app?collection=lock'
    lock: '%env(LOCK_DSN)%'

    # named locks
    lock:
        invoice: ['semaphore', 'redis://r2.docker']
        report: 'semaphore'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<!-- config/packages/lock.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:framework="http://symfony.com/schema/dic/symfony"
    xsi:schemaLocation="http://symfony.com/schema/dic/services
        https://symfony.com/schema/dic/services/services-1.0.xsd
        http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">

    <framework:config>
        <framework:lock>
            <framework:resource>flock</framework:resource>

            <framework:resource>flock:///path/to/file</framework:resource>

            <framework:resource>semaphore</framework:resource>

            <framework:resource>memcached://m1.docker</framework:resource>

            <framework:resource>memcached://m1.docker</framework:resource>
            <framework:resource>memcached://m2.docker</framework:resource>

            <framework:resource>redis://r1.docker</framework:resource>

            <framework:resource>redis://r1.docker</framework:resource>
            <framework:resource>redis://r2.docker</framework:resource>

            <framework:resource>zookeeper://z1.docker</framework:resource>

            <framework:resource>zookeeper://z1.docker,z2.docker</framework:resource>

            <framework:resource>zookeeper://localhost01,localhost02:2181</framework:resource>

            <framework:resource>sqlite:///%kernel.project_dir%/var/lock.db</framework:resource>

            <framework:resource>mysql:host=127.0.0.1;dbname=app</framework:resource>

            <framework:resource>pgsql:host=127.0.0.1;dbname=app</framework:resource>

            <framework:resource>pgsql+advisory:host=127.0.0.1;dbname=app</framework:resource>

            <framework:resource>sqlsrv:server=127.0.0.1;Database=app</framework:resource>

            <framework:resource>oci:host=127.0.0.1;dbname=app</framework:resource>

            <framework:resource>mongodb://127.0.0.1/app?collection=lock</framework:resource>

            <framework:resource>%env(LOCK_DSN)%</framework:resource>

            <!-- named locks -->
            <framework:resource name="invoice">semaphore</framework:resource>
            <framework:resource name="invoice">redis://r2.docker</framework:resource>
            <framework:resource name="report">semaphore</framework:resource>
        </framework:lock>
    </framework:config>
</container>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// config/packages/lock.php
use Symfony\Config\FrameworkConfig;

return static function (FrameworkConfig $framework) {
    $framework->lock()
        ->resource('default', ['flock'])
        ->resource('default', ['flock:///path/to/file'])
        ->resource('default', ['semaphore'])
        ->resource('default', ['memcached://m1.docker'])
        ->resource('default', ['memcached://m1.docker', 'memcached://m2.docker'])
        ->resource('default', ['redis://r1.docker'])
        ->resource('default', ['redis://r1.docker', 'redis://r2.docker'])
        ->resource('default', ['zookeeper://z1.docker'])
        ->resource('default', ['zookeeper://z1.docker,z2.docker'])
        ->resource('default', ['zookeeper://localhost01,localhost02:2181'])
        ->resource('default', ['sqlite:///%kernel.project_dir%/var/lock.db'])
        ->resource('default', ['mysql:host=127.0.0.1;dbname=app'])
        ->resource('default', ['pgsql:host=127.0.0.1;dbname=app'])
        ->resource('default', ['pgsql+advisory:host=127.0.0.1;dbname=app'])
        ->resource('default', ['sqlsrv:server=127.0.0.1;Database=app'])
        ->resource('default', ['oci:host=127.0.0.1;dbname=app'])
        ->resource('default', ['mongodb://127.0.0.1/app?collection=lock'])
        ->resource('default', [env('LOCK_DSN')])

        // named locks
        ->resource('invoice', ['semaphore', 'redis://r2.docker'])
        ->resource('report', ['semaphore'])
    ;
};

6.1

The CSV support (e.g. zookeeper://localhost01,localhost02:2181) in ZookeeperStore DSN was introduced in Symfony 6.1.

Locking a Resource

To lock the default resource, autowire the lock factory using LockFactory:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// src/Controller/PdfController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Lock\LockFactory;

class PdfController extends AbstractController
{
    #[Route('/download/terms-of-use.pdf')]
    public function downloadPdf(LockFactory $factory, MyPdfGeneratorService $pdf)
    {
        $lock = $factory->createLock('pdf-creation');
        $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 use the same lock, inject the LockFactory instead to create a separate lock instance for each service.

Locking a Dynamic Resource

Sometimes the application is able to cut the resource into small pieces in order to lock a small subset of processes and let others through. The previous example showed how to lock the $pdf->getOrCreatePdf() call for everybody, now let's see how to lock a $pdf->getOrCreatePdf($version) call only for processes asking for the same $version:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// src/Controller/PdfController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Lock\LockFactory;

class PdfController extends AbstractController
{
    #[Route('/download/{version}/terms-of-use.pdf')]
    public function downloadPdf($version, LockFactory $lockFactory, MyPdfGeneratorService $pdf)
    {
        $lock = $lockFactory->createLock('pdf-creation-'.$version);
        $lock->acquire(true);

        // heavy computation
        $myPdf = $pdf->getOrCreatePdf($version);

        $lock->release();

        // ...
    }
}

Naming Locks

If the application needs different kind of Stores alongside each other, Symfony provides named lock:

  • YAML
  • XML
  • PHP
1
2
3
4
5
# config/packages/lock.yaml
framework:
    lock:
        invoice: ['semaphore', 'redis://r2.docker']
        report: 'semaphore'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- config/packages/lock.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:framework="http://symfony.com/schema/dic/symfony"
    xsi:schemaLocation="http://symfony.com/schema/dic/services
        https://symfony.com/schema/dic/services/services-1.0.xsd
        http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">

    <framework:config>
        <framework:lock>
            <framework:resource name="invoice">semaphore</framework:resource>
            <framework:resource name="invoice">redis://r2.docker</framework:resource>
            <framework:resource name="report">semaphore</framework:resource>
        </framework:lock>
    </framework:config>
</container>
1
2
3
4
5
6
7
8
9
// config/packages/lock.php
use Symfony\Config\FrameworkConfig;

return static function (FrameworkConfig $framework) {
    $framework->lock()
        ->resource('invoice', ['semaphore', 'redis://r2.docker'])
        ->resource('report', ['semaphore']);
    ;
};

An autowiring alias is created for each named lock with a name using the camel case version of its name suffixed by LockFactory.

For instance, the invoice lock can be injected by naming the argument $invoiceLockFactory and type-hinting it with LockFactory:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/Controller/PdfController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Lock\LockFactory;

class PdfController extends AbstractController
{
    #[Route('/download/terms-of-use.pdf')]
    public function downloadPdf(LockFactory $invoiceLockFactory, MyPdfGeneratorService $pdf)
    {
        // ...
    }
}
This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
We stand with Ukraine.
Version:

Symfony 6.2 is backed by

Symfony 6.2 is backed by

Get your Symfony expertise recognized

Get your Symfony expertise recognized

Check Code Performance in Dev, Test, Staging & Production

Check Code Performance in Dev, Test, Staging & Production

↓ Our footer now uses the colors of the Ukrainian flag because Symfony stands with the people of Ukraine.

Avatar of iarro, a Symfony contributor

Thanks iarro for being a Symfony contributor

1 commit • 6 lines changed

View all contributors that help us make Symfony

Become a Symfony contributor

Be an active part of the community and contribute ideas, code and bug fixes. Both experts and newcomers are welcome.

Learn how to contribute

Symfony™ is a trademark of Symfony SAS. All rights reserved.

  • What is Symfony?
    • Symfony at a Glance
    • Symfony Components
    • Case Studies
    • Symfony Releases
    • Security Policy
    • Logo & Screenshots
    • Trademark & Licenses
    • symfony1 Legacy
  • Learn Symfony
    • Symfony Docs
    • Symfony Book
    • Reference
    • Bundles
    • Best Practices
    • Training
    • eLearning Platform
    • Certification
  • Screencasts
    • Learn Symfony
    • Learn PHP
    • Learn JavaScript
    • Learn Drupal
    • Learn RESTful APIs
  • Community
    • SymfonyConnect
    • Support
    • How to be Involved
    • Code of Conduct
    • Events & Meetups
    • Projects using Symfony
    • Downloads Stats
    • Contributors
    • Backers
  • Blog
    • Events & Meetups
    • A week of symfony
    • Case studies
    • Cloud
    • Community
    • Conferences
    • Diversity
    • Documentation
    • Living on the edge
    • Releases
    • Security Advisories
    • SymfonyInsight
    • Twig
    • SensioLabs
  • Services
    • SensioLabs services
    • Train developers
    • Manage your project quality
    • Improve your project performance
    • Host Symfony projects
    Deployed on
Follow Symfony
Search by Algolia