mirror of
https://github.com/nextcloud/server.git
synced 2026-06-12 18:21:40 -04:00
refactor: CappedMemoryCache class documentation and logic
Signed-off-by: Josh <josh.t.richards@gmail.com>
This commit is contained in:
parent
cf4042dd28
commit
bf853e20f0
1 changed files with 147 additions and 24 deletions
|
|
@ -1,38 +1,111 @@
|
|||
<?php
|
||||
/**
|
||||
* Part of Nextcloud's public caching API: CappedMemoryCache class
|
||||
*/
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2022-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
namespace OCP\Cache;
|
||||
|
||||
use OCP\ICache;
|
||||
|
||||
/**
|
||||
* In-memory cache with a capacity limit to keep memory usage in check
|
||||
* A simple, fast, memory-only FIFO cache with a configurable size limit.
|
||||
*
|
||||
* Uses a simple FIFO expiry mechanism
|
||||
* Features:
|
||||
* - Stores key-value pairs in memory, but only up to a specified capacity.
|
||||
* - Implements standard Nextcloud cache (OCP\ICache) and PHP ArrayAccess interfaces.
|
||||
* - Designed for temporary, in-memory caching in Nextcloud apps or core.
|
||||
* - Uses a simple FIFO expiry mechanism.
|
||||
*
|
||||
* Benefits:
|
||||
* - No administrative configuration/dependencies (always available).
|
||||
* - Can be used via normal method calls (OCP\ICache) or PHP array syntax.
|
||||
* - Automatically removes oldest entries when limit is reached.
|
||||
* - Can be used like an array or via standard Nextcloud cache operations.
|
||||
* - Lowest latency possible.
|
||||
* - Reduces load on distributed cache.
|
||||
* - Offers consumers flexibility to choose best cache combo for a use case
|
||||
*
|
||||
* Caveats:
|
||||
* - Not at all shared (even among processes/transactions/requests on the same host).
|
||||
* - Highly transient (end of process/transaction/request).
|
||||
* - Consumes RAM on the local host.
|
||||
*
|
||||
* Usage examples:
|
||||
*
|
||||
* ```php
|
||||
* [...]
|
||||
* use OCP\Cache\CappedMemoryCache;
|
||||
* [...]
|
||||
* $cache = new CappedMemoryCache(64); // capacity of 64 items
|
||||
* $cache->set('foo', 'bar'); // give key 'foo' value 'bar'
|
||||
* if ($cache->hasKey('foo')) {
|
||||
* echo $cache->get('foo'); // outputs 'bar'
|
||||
* }
|
||||
* ```
|
||||
* Or using array syntax:
|
||||
* ```php
|
||||
* [...]
|
||||
* $cache['baz'] = 'qux';
|
||||
* if (isset($cache['baz']) {
|
||||
* echo $cache['baz']; // outputs 'qux'
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @link https://docs.nextcloud.com/server/latest/developer_manual/basics/caching.html
|
||||
* @link https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/caching_configuration.html
|
||||
*
|
||||
* @since 25.0.0
|
||||
* @template T
|
||||
* @template-implements \ArrayAccess<string,T>
|
||||
* @template-implements \ArrayAccess<string, T>
|
||||
* @since 25.0.0
|
||||
*/
|
||||
class CappedMemoryCache implements ICache, \ArrayAccess {
|
||||
|
||||
/**
|
||||
* Maximum number of entries allowed in the cache.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private int $capacity;
|
||||
/** @var T[] */
|
||||
|
||||
/**
|
||||
* Internal cache data storage.
|
||||
*
|
||||
* @var array<string, T>
|
||||
*/
|
||||
private array $cache = [];
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* Current count of items stored in cache.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private int $itemCount = 0;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param int $capacity Maximum number of items in the cache. Defaults to 512.
|
||||
* @since 25.0.0
|
||||
*/
|
||||
public function __construct(int $capacity = 512) {
|
||||
$this->capacity = $capacity;
|
||||
}
|
||||
|
||||
//
|
||||
// Cache Operations
|
||||
//
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* Checks if the cache contains the specified key.
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool True if the key exists, false otherwise.
|
||||
* @since 25.0.0
|
||||
*/
|
||||
public function hasKey($key): bool {
|
||||
|
|
@ -40,7 +113,10 @@ class CappedMemoryCache implements ICache, \ArrayAccess {
|
|||
}
|
||||
|
||||
/**
|
||||
* @return ?T
|
||||
* Retrieves the value for the specified key from the cache.
|
||||
*
|
||||
* @param string $key
|
||||
* @return T|null The value, or null if the key does not exist.
|
||||
* @since 25.0.0
|
||||
*/
|
||||
public function get($key) {
|
||||
|
|
@ -48,41 +124,68 @@ class CappedMemoryCache implements ICache, \ArrayAccess {
|
|||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* Adds or updates a value in the cache.
|
||||
* If capacity is exceeded, evicts the oldest entries.
|
||||
*
|
||||
* @param string $key
|
||||
* @param T $value
|
||||
* @param int $ttl
|
||||
* @param int $ttl Unused. Included for interface compatibility.
|
||||
* @return bool True on success.
|
||||
* @since 25.0.0
|
||||
* @return bool
|
||||
*/
|
||||
public function set($key, $value, $ttl = 0): bool {
|
||||
if (is_null($key)) {
|
||||
$this->cache[] = $value;
|
||||
} else {
|
||||
$isNewKey = !isset($this->cache[$key]) || $key === null;
|
||||
|
||||
if ($key !== null) {
|
||||
$this->cache[$key] = $value;
|
||||
} else { // for offsetSet() when $key is null
|
||||
$this->cache[] = $value;
|
||||
}
|
||||
|
||||
if ($isNewKey) {
|
||||
$this->itemCount++;
|
||||
}
|
||||
$this->garbageCollect();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified key from the cache.
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool True on success.
|
||||
* @since 25.0.0
|
||||
*/
|
||||
public function remove($key): bool {
|
||||
unset($this->cache[$key]);
|
||||
if (isset($this->cache[$key])) {
|
||||
unset($this->cache[$key]);
|
||||
$this->itemCount--;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* Clears all cache entries.
|
||||
*
|
||||
* @param string $prefix Unused. Included for interface compatibility.
|
||||
* @return bool True on success.
|
||||
* @since 25.0.0
|
||||
*/
|
||||
public function clear($prefix = ''): bool {
|
||||
$this->cache = [];
|
||||
$this->itemCount = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// ArrayAccess Support
|
||||
//
|
||||
|
||||
/**
|
||||
* Determines if an offset exists in the cache.
|
||||
*
|
||||
* @param string $offset
|
||||
* @return bool
|
||||
* @since 25.0.0
|
||||
*/
|
||||
public function offsetExists($offset): bool {
|
||||
|
|
@ -90,17 +193,21 @@ class CappedMemoryCache implements ICache, \ArrayAccess {
|
|||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @return T
|
||||
* Retrieves the value at the specified offset.
|
||||
*
|
||||
* @param string $offset
|
||||
* @return T|null
|
||||
* @since 25.0.0
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function &offsetGet($offset) {
|
||||
// TODO: Any side effects with the new GC tracking?
|
||||
return $this->cache[$offset];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* Sets the value at the specified offset.
|
||||
*
|
||||
* @param string $offset
|
||||
* @param T $value
|
||||
* @since 25.0.0
|
||||
|
|
@ -110,35 +217,51 @@ class CappedMemoryCache implements ICache, \ArrayAccess {
|
|||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* Unsets the value at the specified offset.
|
||||
*
|
||||
* @param string $offset
|
||||
* @since 25.0.0
|
||||
*/
|
||||
public function offsetUnset($offset): void {
|
||||
$this->remove($offset);
|
||||
}
|
||||
|
||||
//
|
||||
// Utilities
|
||||
//
|
||||
|
||||
/**
|
||||
* @return T[]
|
||||
* Returns all cache data as an associative array.
|
||||
*
|
||||
* @return array<string, T>
|
||||
* @since 25.0.0
|
||||
*/
|
||||
public function getData(): array {
|
||||
return $this->cache;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes oldest entries if cache exceeds its capacity.
|
||||
*
|
||||
* @return void
|
||||
* @since 25.0.0
|
||||
*/
|
||||
private function garbageCollect(): void {
|
||||
while (count($this->cache) > $this->capacity) {
|
||||
while ($this->itemCount > $this->capacity) {
|
||||
reset($this->cache);
|
||||
$key = key($this->cache);
|
||||
$this->remove($key);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Static Methods
|
||||
//
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* Indicates if this cache implementation is available.
|
||||
*
|
||||
* @return bool Always returns true.
|
||||
* @since 25.0.0
|
||||
*/
|
||||
public static function isAvailable(): bool {
|
||||
|
|
|
|||
Loading…
Reference in a new issue