vendor/symfony/lock/Lock.php line 205

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\Lock;
  11. use Psr\Log\LoggerAwareInterface;
  12. use Psr\Log\LoggerAwareTrait;
  13. use Psr\Log\NullLogger;
  14. use Symfony\Component\Lock\Exception\InvalidArgumentException;
  15. use Symfony\Component\Lock\Exception\LockAcquiringException;
  16. use Symfony\Component\Lock\Exception\LockConflictedException;
  17. use Symfony\Component\Lock\Exception\LockExpiredException;
  18. use Symfony\Component\Lock\Exception\LockReleasingException;
  19. /**
  20.  * Lock is the default implementation of the LockInterface.
  21.  *
  22.  * @author Jérémy Derussé <jeremy@derusse.com>
  23.  */
  24. final class Lock implements SharedLockInterfaceLoggerAwareInterface
  25. {
  26.     use LoggerAwareTrait;
  27.     private $store;
  28.     private $key;
  29.     private $ttl;
  30.     private $autoRelease;
  31.     private $dirty false;
  32.     /**
  33.      * @param float|null $ttl         Maximum expected lock duration in seconds
  34.      * @param bool       $autoRelease Whether to automatically release the lock or not when the lock instance is destroyed
  35.      */
  36.     public function __construct(Key $keyPersistingStoreInterface $store, ?float $ttl nullbool $autoRelease true)
  37.     {
  38.         $this->store $store;
  39.         $this->key $key;
  40.         $this->ttl $ttl;
  41.         $this->autoRelease $autoRelease;
  42.         $this->logger = new NullLogger();
  43.     }
  44.     public function __sleep(): array
  45.     {
  46.         throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
  47.     }
  48.     public function __wakeup()
  49.     {
  50.         throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
  51.     }
  52.     /**
  53.      * Automatically releases the underlying lock when the object is destructed.
  54.      */
  55.     public function __destruct()
  56.     {
  57.         if (!$this->autoRelease || !$this->dirty || !$this->isAcquired()) {
  58.             return;
  59.         }
  60.         $this->release();
  61.     }
  62.     /**
  63.      * {@inheritdoc}
  64.      */
  65.     public function acquire(bool $blocking false): bool
  66.     {
  67.         $this->key->resetLifetime();
  68.         try {
  69.             if ($blocking) {
  70.                 if (!$this->store instanceof BlockingStoreInterface) {
  71.                     while (true) {
  72.                         try {
  73.                             $this->store->save($this->key);
  74.                             break;
  75.                         } catch (LockConflictedException $e) {
  76.                             usleep((100 random_int(-1010)) * 1000);
  77.                         }
  78.                     }
  79.                 } else {
  80.                     $this->store->waitAndSave($this->key);
  81.                 }
  82.             } else {
  83.                 $this->store->save($this->key);
  84.             }
  85.             $this->dirty true;
  86.             $this->logger->debug('Successfully acquired the "{resource}" lock.', ['resource' => $this->key]);
  87.             if ($this->ttl) {
  88.                 $this->refresh();
  89.             }
  90.             if ($this->key->isExpired()) {
  91.                 try {
  92.                     $this->release();
  93.                 } catch (\Exception $e) {
  94.                     // swallow exception to not hide the original issue
  95.                 }
  96.                 throw new LockExpiredException(sprintf('Failed to store the "%s" lock.'$this->key));
  97.             }
  98.             return true;
  99.         } catch (LockConflictedException $e) {
  100.             $this->dirty false;
  101.             $this->logger->info('Failed to acquire the "{resource}" lock. Someone else already acquired the lock.', ['resource' => $this->key]);
  102.             if ($blocking) {
  103.                 throw $e;
  104.             }
  105.             return false;
  106.         } catch (\Exception $e) {
  107.             $this->logger->notice('Failed to acquire the "{resource}" lock.', ['resource' => $this->key'exception' => $e]);
  108.             throw new LockAcquiringException(sprintf('Failed to acquire the "%s" lock.'$this->key), 0$e);
  109.         }
  110.     }
  111.     /**
  112.      * {@inheritdoc}
  113.      */
  114.     public function acquireRead(bool $blocking false): bool
  115.     {
  116.         $this->key->resetLifetime();
  117.         try {
  118.             if (!$this->store instanceof SharedLockStoreInterface) {
  119.                 $this->logger->debug('Store does not support ReadLocks, fallback to WriteLock.', ['resource' => $this->key]);
  120.                 return $this->acquire($blocking);
  121.             }
  122.             if ($blocking) {
  123.                 if (!$this->store instanceof BlockingSharedLockStoreInterface) {
  124.                     while (true) {
  125.                         try {
  126.                             $this->store->saveRead($this->key);
  127.                             break;
  128.                         } catch (LockConflictedException $e) {
  129.                             usleep((100 random_int(-1010)) * 1000);
  130.                         }
  131.                     }
  132.                 } else {
  133.                     $this->store->waitAndSaveRead($this->key);
  134.                 }
  135.             } else {
  136.                 $this->store->saveRead($this->key);
  137.             }
  138.             $this->dirty true;
  139.             $this->logger->debug('Successfully acquired the "{resource}" lock for reading.', ['resource' => $this->key]);
  140.             if ($this->ttl) {
  141.                 $this->refresh();
  142.             }
  143.             if ($this->key->isExpired()) {
  144.                 try {
  145.                     $this->release();
  146.                 } catch (\Exception $e) {
  147.                     // swallow exception to not hide the original issue
  148.                 }
  149.                 throw new LockExpiredException(sprintf('Failed to store the "%s" lock.'$this->key));
  150.             }
  151.             return true;
  152.         } catch (LockConflictedException $e) {
  153.             $this->dirty false;
  154.             $this->logger->info('Failed to acquire the "{resource}" lock. Someone else already acquired the lock.', ['resource' => $this->key]);
  155.             if ($blocking) {
  156.                 throw $e;
  157.             }
  158.             return false;
  159.         } catch (\Exception $e) {
  160.             $this->logger->notice('Failed to acquire the "{resource}" lock.', ['resource' => $this->key'exception' => $e]);
  161.             throw new LockAcquiringException(sprintf('Failed to acquire the "%s" lock.'$this->key), 0$e);
  162.         }
  163.     }
  164.     /**
  165.      * {@inheritdoc}
  166.      */
  167.     public function refresh(?float $ttl null)
  168.     {
  169.         if (null === $ttl) {
  170.             $ttl $this->ttl;
  171.         }
  172.         if (!$ttl) {
  173.             throw new InvalidArgumentException('You have to define an expiration duration.');
  174.         }
  175.         try {
  176.             $this->key->resetLifetime();
  177.             $this->store->putOffExpiration($this->key$ttl);
  178.             $this->dirty true;
  179.             if ($this->key->isExpired()) {
  180.                 try {
  181.                     $this->release();
  182.                 } catch (\Exception $e) {
  183.                     // swallow exception to not hide the original issue
  184.                 }
  185.                 throw new LockExpiredException(sprintf('Failed to put off the expiration of the "%s" lock within the specified time.'$this->key));
  186.             }
  187.             $this->logger->debug('Expiration defined for "{resource}" lock for "{ttl}" seconds.', ['resource' => $this->key'ttl' => $ttl]);
  188.         } catch (LockConflictedException $e) {
  189.             $this->dirty false;
  190.             $this->logger->notice('Failed to define an expiration for the "{resource}" lock, someone else acquired the lock.', ['resource' => $this->key]);
  191.             throw $e;
  192.         } catch (\Exception $e) {
  193.             $this->logger->notice('Failed to define an expiration for the "{resource}" lock.', ['resource' => $this->key'exception' => $e]);
  194.             throw new LockAcquiringException(sprintf('Failed to define an expiration for the "%s" lock.'$this->key), 0$e);
  195.         }
  196.     }
  197.     /**
  198.      * {@inheritdoc}
  199.      */
  200.     public function isAcquired(): bool
  201.     {
  202.         return $this->dirty $this->store->exists($this->key);
  203.     }
  204.     /**
  205.      * {@inheritdoc}
  206.      */
  207.     public function release()
  208.     {
  209.         try {
  210.             try {
  211.                 $this->store->delete($this->key);
  212.                 $this->dirty false;
  213.             } catch (LockReleasingException $e) {
  214.                 throw $e;
  215.             } catch (\Exception $e) {
  216.                 throw new LockReleasingException(sprintf('Failed to release the "%s" lock.'$this->key), 0$e);
  217.             }
  218.             if ($this->store->exists($this->key)) {
  219.                 throw new LockReleasingException(sprintf('Failed to release the "%s" lock, the resource is still locked.'$this->key));
  220.             }
  221.         } catch (LockReleasingException $e) {
  222.             $this->logger->notice('Failed to release the "{resource}" lock.', ['resource' => $this->key]);
  223.             throw $e;
  224.         }
  225.     }
  226.     /**
  227.      * {@inheritdoc}
  228.      */
  229.     public function isExpired(): bool
  230.     {
  231.         return $this->key->isExpired();
  232.     }
  233.     /**
  234.      * {@inheritdoc}
  235.      */
  236.     public function getRemainingLifetime(): ?float
  237.     {
  238.         return $this->key->getRemainingLifetime();
  239.     }
  240. }