Skip to content

Commit

Permalink
[Cache] Use sub-second accuracy for internal expiry calculations
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolas-grekas committed May 29, 2018
1 parent 7ec3d50 commit cedff2d
Show file tree
Hide file tree
Showing 9 changed files with 19 additions and 20 deletions.
6 changes: 2 additions & 4 deletions src/Symfony/Component/Cache/Adapter/AbstractAdapter.php
Expand Up @@ -65,15 +65,13 @@ function ($key, $value, $isHit) use ($defaultLifetime) {
$this->mergeByLifetime = \Closure::bind(
function ($deferred, $namespace, &$expiredIds) use ($getId) {
$byLifetime = array();
$now = time();
$now = microtime(true);
$expiredIds = array();

foreach ($deferred as $key => $item) {
if (null === $item->expiry) {
$ttl = 0 < $item->defaultLifetime ? $item->defaultLifetime : 0;
} elseif ($item->expiry > $now) {
$ttl = $item->expiry - $now;
} else {
} elseif (0 >= $ttl = (int) ($item->expiry - $now)) {
$expiredIds[] = $getId($key);
continue;
}
Expand Down
6 changes: 3 additions & 3 deletions src/Symfony/Component/Cache/Adapter/ArrayAdapter.php
Expand Up @@ -87,7 +87,7 @@ public function getItems(array $keys = array())
CacheItem::validateKey($key);
}

return $this->generateItems($keys, time(), $this->createCacheItem);
return $this->generateItems($keys, microtime(true), $this->createCacheItem);
}

/**
Expand Down Expand Up @@ -115,7 +115,7 @@ public function save(CacheItemInterface $item)
$value = $item["\0*\0value"];
$expiry = $item["\0*\0expiry"];

if (null !== $expiry && $expiry <= time()) {
if (null !== $expiry && $expiry <= microtime(true)) {
$this->deleteItem($key);

return true;
Expand All @@ -131,7 +131,7 @@ public function save(CacheItemInterface $item)
}
}
if (null === $expiry && 0 < $item["\0*\0defaultLifetime"]) {
$expiry = time() + $item["\0*\0defaultLifetime"];
$expiry = microtime(true) + $item["\0*\0defaultLifetime"];
}

$this->values[$key] = $value;
Expand Down
4 changes: 2 additions & 2 deletions src/Symfony/Component/Cache/Adapter/ProxyAdapter.php
Expand Up @@ -73,7 +73,7 @@ function (CacheItemInterface $innerItem, array $item) {
$item["\0*\0value"] = array("\x9D".pack('VN', $stats[CacheItem::STATS_EXPIRY] - CacheItem::STATS_EXPIRY_OFFSET, $stats[CacheItem::STATS_CTIME])."\x5F" => $item["\0*\0value"]);
}
$innerItem->set($item["\0*\0value"]);
$innerItem->expiresAt(null !== $item["\0*\0expiry"] ? \DateTime::createFromFormat('U', $item["\0*\0expiry"]) : null);
$innerItem->expiresAt(null !== $item["\0*\0expiry"] ? \DateTime::createFromFormat('U', (int) $item["\0*\0expiry"]) : null);
},
null,
CacheItem::class
Expand Down Expand Up @@ -192,7 +192,7 @@ private function doSave(CacheItemInterface $item, $method)
}
$item = (array) $item;
if (null === $item["\0*\0expiry"] && 0 < $item["\0*\0defaultLifetime"]) {
$item["\0*\0expiry"] = time() + $item["\0*\0defaultLifetime"];
$item["\0*\0expiry"] = microtime(true) + $item["\0*\0defaultLifetime"];
}
$innerItem = $item["\0*\0poolHash"] === $this->poolHash ? $item["\0*\0innerItem"] : $this->pool->getItem($this->namespace.$item["\0*\0key"]);
($this->setInnerItem)($innerItem, $item);
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/Cache/CHANGELOG.md
Expand Up @@ -4,6 +4,7 @@ CHANGELOG
4.2.0
-----

* added sub-second expiry accuracy for backends that support it
* added `CacheInterface` and `TaggableCacheInterface`, providing stampede protection via probabilistic early expiration
* throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool
* deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getStats()` instead
Expand Down
10 changes: 5 additions & 5 deletions src/Symfony/Component/Cache/CacheItem.php
Expand Up @@ -89,9 +89,9 @@ public function set($value)
public function expiresAt($expiration)
{
if (null === $expiration) {
$this->expiry = $this->defaultLifetime > 0 ? time() + $this->defaultLifetime : null;
$this->expiry = $this->defaultLifetime > 0 ? microtime(true) + $this->defaultLifetime : null;
} elseif ($expiration instanceof \DateTimeInterface) {
$this->expiry = (int) $expiration->format('U');
$this->expiry = (float) $expiration->format('U.u');
} else {
throw new InvalidArgumentException(sprintf('Expiration date must implement DateTimeInterface or be null, "%s" given', is_object($expiration) ? get_class($expiration) : gettype($expiration)));
}
Expand All @@ -105,11 +105,11 @@ public function expiresAt($expiration)
public function expiresAfter($time)
{
if (null === $time) {
$this->expiry = $this->defaultLifetime > 0 ? time() + $this->defaultLifetime : null;
$this->expiry = $this->defaultLifetime > 0 ? microtime(true) + $this->defaultLifetime : null;
} elseif ($time instanceof \DateInterval) {
$this->expiry = (int) \DateTime::createFromFormat('U', time())->add($time)->format('U');
$this->expiry = microtime(true) + \DateTime::createFromFormat('U', 0)->add($time)->format('U.u');
} elseif (\is_int($time)) {
$this->expiry = $time + time();
$this->expiry = $time + microtime(true);
} else {
throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given', is_object($time) ? get_class($time) : gettype($time)));
}
Expand Down
4 changes: 2 additions & 2 deletions src/Symfony/Component/Cache/Simple/ArrayCache.php
Expand Up @@ -64,7 +64,7 @@ public function getMultiple($keys, $default = null)
CacheItem::validateKey($key);
}

return $this->generateItems($keys, time(), function ($k, $v, $hit) use ($default) { return $hit ? $v : $default; });
return $this->generateItems($keys, microtime(true), function ($k, $v, $hit) use ($default) { return $hit ? $v : $default; });
}

/**
Expand Down Expand Up @@ -121,7 +121,7 @@ public function setMultiple($values, $ttl = null)
}
}
}
$expiry = 0 < $ttl ? time() + $ttl : PHP_INT_MAX;
$expiry = 0 < $ttl ? microtime(true) + $ttl : PHP_INT_MAX;

foreach ($valuesArray as $key => $value) {
$this->values[$key] = $value;
Expand Down
4 changes: 2 additions & 2 deletions src/Symfony/Component/Cache/Tests/Fixtures/ArrayCache.php
Expand Up @@ -21,12 +21,12 @@ protected function doContains($id)

$expiry = $this->data[$id][1];

return !$expiry || time() <= $expiry || !$this->doDelete($id);
return !$expiry || microtime(true) <= $expiry || !$this->doDelete($id);
}

protected function doSave($id, $data, $lifeTime = 0)
{
$this->data[$id] = array($data, $lifeTime ? time() + $lifeTime : false);
$this->data[$id] = array($data, $lifeTime ? microtime(true) + $lifeTime : false);

return true;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/Cache/Traits/ArrayTrait.php
Expand Up @@ -44,7 +44,7 @@ public function hasItem($key)
{
CacheItem::validateKey($key);

return isset($this->expiries[$key]) && ($this->expiries[$key] >= time() || !$this->deleteItem($key));
return isset($this->expiries[$key]) && ($this->expiries[$key] >= microtime(true) || !$this->deleteItem($key));
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/Cache/Traits/GetTrait.php
Expand Up @@ -69,7 +69,7 @@ private function doGet(CacheItemPoolInterface $pool, string $key, callable $call
$save = \Closure::bind(
function (CacheItemPoolInterface $pool, CacheItemInterface $item, $value, float $startTime) {
if ($item instanceof CacheItem && $startTime && $item->expiry > $endTime = microtime(true)) {
$item->newStats[CacheItem::STATS_EXPIRY] = $item->expiry;
$item->newStats[CacheItem::STATS_EXPIRY] = (int) $item->expiry;
$item->newStats[CacheItem::STATS_CTIME] = 1000 * (int) ($endTime - $startTime);
}
$pool->save($item->set($value));
Expand Down

0 comments on commit cedff2d

Please sign in to comment.