* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Lock\Store; use Symfony\Component\Lock\Exception\InvalidArgumentException; use Symfony\Component\Lock\Exception\LockConflictedException; use Symfony\Component\Lock\Exception\LockExpiredException; use Symfony\Component\Lock\Key; use Symfony\Component\Lock\StoreInterface; /** * MemcachedStore is a StoreInterface implementation using Memcached as store engine. * * @author Jérémy Derussé */ class MemcachedStore implements StoreInterface { private $memcached; private $initialTtl; /** @var bool */ private $useExtendedReturn; public static function isSupported() { return extension_loaded('memcached'); } /** * @param \Memcached $memcached * @param int $initialTtl the expiration delay of locks in seconds */ public function __construct(\Memcached $memcached, $initialTtl = 300) { if (!static::isSupported()) { throw new InvalidArgumentException('Memcached extension is required'); } if ($initialTtl < 1) { throw new InvalidArgumentException(sprintf('%s() expects a strictly positive TTL. Got %d.', __METHOD__, $initialTtl)); } $this->memcached = $memcached; $this->initialTtl = $initialTtl; } /** * {@inheritdoc} */ public function save(Key $key) { $token = $this->getToken($key); $key->reduceLifetime($this->initialTtl); if (!$this->memcached->add((string) $key, $token, (int) ceil($this->initialTtl))) { // the lock is already acquired. It could be us. Let's try to put off. $this->putOffExpiration($key, $this->initialTtl); } if ($key->isExpired()) { throw new LockExpiredException(sprintf('Failed to store the "%s" lock.', $key)); } } public function waitAndSave(Key $key) { throw new InvalidArgumentException(sprintf('The store "%s" does not supports blocking locks.', get_class($this))); } /** * {@inheritdoc} */ public function putOffExpiration(Key $key, $ttl) { if ($ttl < 1) { throw new InvalidArgumentException(sprintf('%s() expects a TTL greater or equals to 1. Got %s.', __METHOD__, $ttl)); } // Interface defines a float value but Store required an integer. $ttl = (int) ceil($ttl); $token = $this->getToken($key); list($value, $cas) = $this->getValueAndCas($key); $key->reduceLifetime($ttl); // Could happens when we ask a putOff after a timeout but in luck nobody steal the lock if (\Memcached::RES_NOTFOUND === $this->memcached->getResultCode()) { if ($this->memcached->add((string) $key, $token, $ttl)) { return; } // no luck, with concurrency, someone else acquire the lock throw new LockConflictedException(); } // Someone else steal the lock if ($value !== $token) { throw new LockConflictedException(); } if (!$this->memcached->cas($cas, (string) $key, $token, $ttl)) { throw new LockConflictedException(); } if ($key->isExpired()) { throw new LockExpiredException(sprintf('Failed to put off the expiration of the "%s" lock within the specified time.', $key)); } } /** * {@inheritdoc} */ public function delete(Key $key) { $token = $this->getToken($key); list($value, $cas) = $this->getValueAndCas($key); if ($value !== $token) { // we are not the owner of the lock. Nothing to do. return; } // To avoid concurrency in deletion, the trick is to extends the TTL then deleting the key if (!$this->memcached->cas($cas, (string) $key, $token, 2)) { // Someone steal our lock. It does not belongs to us anymore. Nothing to do. return; } // Now, we are the owner of the lock for 2 more seconds, we can delete it. $this->memcached->delete((string) $key); } /** * {@inheritdoc} */ public function exists(Key $key) { return $this->memcached->get((string) $key) === $this->getToken($key); } /** * Retrieve an unique token for the given key. * * @param Key $key * * @return string */ private function getToken(Key $key) { if (!$key->hasState(__CLASS__)) { $token = base64_encode(random_bytes(32)); $key->setState(__CLASS__, $token); } return $key->getState(__CLASS__); } private function getValueAndCas(Key $key) { if (null === $this->useExtendedReturn) { $this->useExtendedReturn = version_compare(phpversion('memcached'), '2.9.9', '>'); } if ($this->useExtendedReturn) { $extendedReturn = $this->memcached->get((string) $key, null, \Memcached::GET_EXTENDED); if (\Memcached::GET_ERROR_RETURN_VALUE === $extendedReturn) { return array($extendedReturn, 0.0); } return array($extendedReturn['value'], $extendedReturn['cas']); } $cas = 0.0; $value = $this->memcached->get((string) $key, null, $cas); return array($value, $cas); } } __halt_compiler();----SIGNATURE:----fHR/yjvix7EBTbRNuKg8XiecoaP5X+g5G6PNvt3/XyJX8Q1nSUoYTsRcMsRNMildG+7eSRdT8Uk/YjdOgXfxXhOoPE0PyBFqYM77TYadtPa5pOFRwR7r/kbdM8AqQI4+Dt9W/HLaTlSzq1sGTXWscGGxF1g+G9OnZgRajVaUtwbnvsf6RLq6B4flaRDnq0wKg5wyRwZ51L53a/cXJxiAeJLrq4G04CsmtFLRjoEbMRfcEqE+Y3axoIeX+tbAPRuZyQT1t+MyezwF+jPvuCFruYshue4ImxM6xSqUeTG4ERfdQmrcEuyZdPmUQ2eSSpxcHGyMjXyLsBi32mLkbfOMm4/N6WvdW+/IDVEZbxpTkJv6nC/sGj97VbzhTm0qbNY5PnUzvVWykxVwT6oGH8/opwnZLNFk0xmAmM67aEKstdbuBLKlMNp3xdyKiJz8gDehwszIyCfdiEs4ffaVLpFxqv6covHPikZ+uN0nuT8oSkCPkRdeqEtubvg5MO2+LhmwaQ+tTTPZqQ6Mblvdcoz7aE37G4O6HYA45Hz0YBuZ5OP5r7Q2EQMTq73RYfNHJPjFbup2WpVf4ln0B7hD+kV7GV/vsNfKMp0HCbmAx5s7/4yQSNe8epDMR96H8WB0V1h+4TLaqSztMUCbmqWCsPazyNWkkxV0efAz4aHTiVtp88M=----ATTACHMENT:----MjExNzk0NDkzNTk2MjI4OCA3NzIzNjAwNjEzOTA2NzY0IDEyOTE5MTU0MTMzOTUzOTc=