public function getMulti(array $keys, array &$tokens = null)
{
// fetch both requested keys + stampede protection indicators at once
$stampedeKeys = array_combine($keys, array_map(array($this, 'stampedeKey'), $keys));
$values = $this->cache->getMulti(array_merge($keys, $stampedeKeys), $tokens);
// figure out which of the requested keys are protected, and which need
// protection (=currently empty & not yet protected)
$protected = array_keys(array_intersect($stampedeKeys, array_keys($values)));
$protect = array_diff($keys, array_keys($values), $protected);
// protect keys that we couldn't find, and remove them from the list of
// keys we want results from, because we'll keep fetching empty keys
// (that are currently protected)
$done = $this->protect($protect);
$keys = array_diff($keys, $done);
// we may have failed to protect some keys after all (race condition
// with another process), in which case we also have to keep polling
// those keys (which the other process is likely working on already)
$protected += array_diff($protect, $done);
// we over-fetched (to include stampede indicators), now limit the
// results to only the keys we requested
$results = array_intersect_key($values, array_flip($keys));
$tokens = array_intersect_key($tokens, $results);
// we may not have been able to retrieve all keys yet: some may have
// been "protected" (and are being regenerated in another process) in
// which case we'll retry a couple of times, hoping the other process
// stores the new value in the meantime
$attempts = $this->attempts;
while (--$attempts > 0 && !empty($protected) && $this->sleep()) {
$values = $this->cache->getMulti($protected, $tokens2);
$results += array_intersect_key($values, array_flip($keys));
$tokens += array_intersect_key($tokens2, array_flip($keys));
// don't keep polling for values we just fetched...
$protected = array_diff($protected, array_keys($values));
}
return $results;
}