/** * {@inheritdoc} */ public function getMulti(array $keys, array &$tokens = null) { // retrieve all that we can from local cache $values = $this->local->getMulti($keys); $tokens = array(); // short-circuit reading from real cache if we have an uncommitted flush if (!$this->suspend) { // figure out which missing key we need to get from real cache $keys = array_diff($keys, array_keys($values)); foreach ($keys as $i => $key) { // don't reach out to real cache for keys that are about to be gone if ($this->local->expired($key)) { unset($keys[$i]); } } // fetch missing values from real cache if ($keys) { $missing = $this->cache->getMulti($keys); $values += $missing; } } // any tokens we get will be unreliable, so generate some replacements // (more elaborate explanation in get()) foreach ($values as $key => $value) { $token = uniqid(); $tokens[$key] = $token; $this->tokens[$token] = serialize($value); } return $values; }
/** * {@inheritdoc} */ public function has($key) { if (!is_string($key)) { throw new InvalidArgumentException('Invalid key: ' . serialize($key) . '. Must be string.'); } // KeyValueStore::get returns false for cache misses (which could also // be confused for a `false` value), so we'll check existence with getMulti $multi = $this->store->getMulti(array($key)); return isset($multi[$key]); }
/** * As soon as a key turns up empty (doesn't yet exist in cache), we'll * "protect" it for some time. This will be done by writing to a key similar * to the original key name. If this key is present (which it will only be * for a short amount of time) we'll know it's protected. * * @param array $keys * * @return string[] Array of keys that were successfully protected */ protected function protect(array $keys) { if (empty($keys)) { return array(); } $success = array(); foreach ($keys as $key) { /* * Key is add()ed because there may be multiple concurrent processes * that are both in the process of protecting - first one to add() * wins (and those are returned by the function, so those that are * failed to protect can be considered protected) */ $success[$key] = $this->cache->add($this->stampedeKey($key), '', $this->sla); } return array_keys(array_filter($success)); }
/** * Since we can't perform true atomic transactions, we'll fake it. * Most of the operations (set, touch, ...) can't fail. We'll do those last. * We'll first schedule the operations that can fail (cas, replace, add) * to minimize chances of another process overwriting those values in the * meantime. * But it could still happen, so we should fetch the current values for all * unsafe operations. If the transaction fails, we can then restore them. * * @return array[] Array of 2 [key => value] maps: current & scheduled data */ protected function generateRollback() { $keys = array(); $new = array(); foreach ($this->keys as $key => $data) { $operation = $data[0]; // we only need values for cas & replace - recovering from an 'add' // is just deleting the value... if (in_array($operation, array('cas', 'replace'))) { $keys[] = $key; $new[$key] = $data[2]['value']; } } if (empty($keys)) { return array(array(), array()); } // fetch the existing data & return the planned new data as well $current = $this->cache->getMulti($keys); return array($current, $new); }
/** * Resolve all unresolved keys at once. */ protected function resolve() { $keys = array_unique(array_values($this->unresolved)); $values = $this->store->getMulti($keys); foreach ($this->unresolved as $unique => $key) { if (!array_key_exists($key, $values)) { // key doesn't exist in cache continue; } /* * In theory, there could've been multiple unresolved requests for * the same cache key. In the case of objects, we'll clone them * to make sure that when the value for 1 item is manipulated, it * doesn't affect the value of the other item (because those objects * would be passed by-ref without the cloning) */ $value = $values[$key]; $value = is_object($value) ? clone $value : $value; $this->resolved[$unique] = $value; } $this->unresolved = array(); }
/** * {@inheritdoc} */ public function commit() { $deferred = array(); foreach ($this->deferred as $key => $item) { if ($item->isExpired()) { // already expired: don't even save it continue; } // setMulti doesn't allow to set expiration times on a per-item basis, // so we'll have to group our requests per expiration date $expire = $item->getExpiration(); $deferred[$expire][$item->getKey()] = $item->get(); } // setMulti doesn't allow to set expiration times on a per-item basis, // so we'll have to group our requests per expiration date $success = true; foreach ($deferred as $expire => $items) { $status = $this->store->setMulti($items, $expire); $success &= !in_array(false, $status); unset($deferred[$expire]); } return (bool) $success; }