Jérémy Derussé
Contributed by Jérémy Derussé in #21093

WARNING: This component was finally removed from Symfony 3.3 and postponed for Symfony 3.4, to have more time to polish and stabilize it.


In computer science, a lock is "a synchronization mechanism for enforcing limits on access to a resource in an environment where there are many threads of execution". In Symfony applications locks are used for example to prevent more than one simultaneous execution of a given command in the server.

In Symfony 2.6 we added a LockHandler utility to simplify the creation of locks. Internally, this utility uses PHP's flock() function, so it's based on the file system and cannot create locks shared in different servers.

In Symfony 3.3 we decided to extend this mechanism and we created a brand new Lock component with these features:

  • It supports multiple lock stores: flock(), PHP Semaphore extension, Redis and Memcache;
  • It creates blocking, non-blocking and auto-expiring locks;
  • It allows combining several stores (e.g. Redis and flock simultaneously) and it can apply different strategies (consensus, majority).

When used as an independent component, you first define the store to use and then create a factory used to create the locks:

1
2
3
4
5
use Symfony\Component\Lock\Factory;
use Symfony\Component\Lock\Store\SemaphoreStore;

$store = new SemaphoreStore();
$factory = new Factory($store);

Then, you can acquire, check, renew or release the locks:

1
2
3
4
5
6
7
8
9
// ...
$lock = $factory->createLock('pdf-invoice-generation');

if ($lock->acquire()) {
    // The resource "pdf-invoice-generation" is locked.
    // You can compute and generate invoice safely here.

    $lock->release();
}

When used within the Symfony framework, its usage is greatly simplified. First, define the type of lock to use (you can define more than one lock type if needed):

1
2
3
4
5
6
7
8
9
# app/config/config.yml
framework:
    # these are all the supported flock stores
    lock: 'flock'
    lock: 'semaphore'
    lock: 'memcached://m1.docker'
    lock: ['memcached://m1.docker', 'memcached://m2.docker']
    lock: 'redis://r1.docker'
    lock: ['redis://r1.docker', 'redis://r2.docker']

Then, use the lock service to acquire, check, renew or release the locks:

1
2
3
4
5
6
7
8
9
10
11
// non-blocking, non-expiring
$lock = $container->get('lock')->acquire();
// blocking, non-expiring (waits indefinitely until the lock is acquired)
$lock = $container->get('lock')->acquire(true);
// non-blocking, expiring in 60 seconds (unless the lock is refreshed)
$lock = $container->get('lock')->acquire(false, 60);

// normal operations on the lock
$lock->isAcquired();
$lock->refresh();
$lock->release();

The documentation of the component is ready but not merged yet and the integration with the Symfony framework is also defined but pending to be merged.

Published in #Living on the edge