-
-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Psr\SimpleCache\CacheInterface::clear() has no effect on hasAvailableSpace() #5
Comments
This test code reproduces the problem for me:
|
I have tried fixing this using After some research I have ended up here: https://github.com/laminas/laminas-cache/blob/2.10.x/src/Storage/Adapter/AbstractAdapter.php#L758 A quick experiment not using an |
So you say it's actually a laminas-cache issue? BTW I thought that PHP might never free allocated memory as reported by <?php
function pm() { echo (memory_get_usage(true) / 1048576) . PHP_EOL; }
pm();
$a = [];
for ($i=0; $i<100_000; ++$i) {
$o = new stdClass();
$o->x = $i;
$a[] = $o;
if ($i % 10_000 === 0) {
pm();
}
}
pm();
unset($a);
pm();
gc_mem_caches();
pm(); prints:
|
Thanks to Michael Weimann Signed-off-by: Maximilian Bösing <2189546+boesing@users.noreply.github.com>
Thanks to Michael Weimann Signed-off-by: Maximilian Bösing <2189546+boesing@users.noreply.github.com>
Thanks to Michael Weimann Signed-off-by: Maximilian Bösing <2189546+boesing@users.noreply.github.com>
Thanks for reporting this and thanks @weeman1337 for the failing test. I've tested few things which did not work. Lets see if we can find the issue. |
Thanks to Michael Weimann Signed-off-by: Maximilian Bösing <2189546+boesing@users.noreply.github.com>
Thanks to Michael Weimann Signed-off-by: Maximilian Bösing <2189546+boesing@users.noreply.github.com>
@InvisibleSmiley @weeman1337 might review #11? |
I guess the same error appears when only clearing some namespaces in case multiple namespaces are used? |
Is this still a thing or did you switched to a non-laminas-cache-based PSR-16 library meanwhile? |
We still use this (or are at least trying to). |
As discussed in laminas/laminas-cache#14 I tried laminas/laminas-cache 4.0.x together with an adapted version of this memory adapter to see whether the signature changes (esp. those reducing the arguments passed by reference) improved the situation. The answer is no. Next I tried to replace ArrayObject with array but that didn't help either. Then I started looking for memory profilers and gave https://github.com/arnaud-lb/php-memory-profiler a go. At first I thought maybe PHPUnit is the culprit so I rewrote the test to run standalone. Didn't help either. Then I replaced the Composer autoloader by hardcoded "require" statements (which obviously need to be put in the correct order, i.e. interfaces before interface implementations etc.). This did the trick, i.e. the "real" PHP memory usage finally dropped after flushing the cache. I don't have the time to dig deeper or try to investigate possible "fixes" to the Composer autoloader so this is where it ends for me. Unless someone else is willing to look into it, I guess the only alternative would be to change all occurrences of For the sake of completeness, here's the final version of my test script. <?php
declare(strict_types=1);
use Laminas\Cache\Exception\OutOfSpaceException;
use Laminas\Cache\Storage\Adapter\Memory;
use Laminas\Cache\Storage\Adapter\MemoryOptions;
use Webmozart\Assert\Assert;
final class MemoryTest
{
private const MEM_STEPPING = 2 * 1024 * 1024; // 2 MB
private const LOOP_LIMIT = 10_000;
public function testReclaimMemory(): void
{
//$loader = require_once __DIR__. '/vendor/autoload.php';
require_once 'vendor/webmozart/assert/src/Mixin.php';
require_once 'vendor/webmozart/assert/src/Assert.php';
require_once 'vendor/laminas/laminas-cache/src/Storage/StorageInterface.php';
require_once 'vendor/laminas/laminas-eventmanager/src/EventInterface.php';
require_once 'vendor/laminas/laminas-eventmanager/src/Event.php';
require_once 'vendor/laminas/laminas-eventmanager/src/SharedEventsCapableInterface.php';
require_once 'vendor/laminas/laminas-eventmanager/src/EventManagerInterface.php';
require_once 'vendor/laminas/laminas-eventmanager/src/EventManager.php';
require_once 'vendor/laminas/laminas-eventmanager/src/EventsCapableInterface.php';
require_once 'vendor/laminas/laminas-eventmanager/src/ResponseCollection.php';
require_once 'vendor/laminas/laminas-cache/src/Storage/PluginCapableInterface.php';
require_once 'vendor/laminas/laminas-cache/src/Storage/PluginAwareInterface.php';
require_once 'vendor/laminas/laminas-cache/src/Storage/AvailableSpaceCapableInterface.php';
require_once 'vendor/laminas/laminas-cache/src/Storage/ClearByPrefixInterface.php';
require_once 'vendor/laminas/laminas-cache/src/Storage/ClearByNamespaceInterface.php';
require_once 'vendor/laminas/laminas-cache/src/Storage/ClearExpiredInterface.php';
require_once 'vendor/laminas/laminas-cache/src/Storage/FlushableInterface.php';
require_once 'vendor/laminas/laminas-cache/src/Storage/IterableInterface.php';
require_once 'vendor/laminas/laminas-cache/src/Storage/TaggableInterface.php';
require_once 'vendor/laminas/laminas-cache/src/Storage/TotalSpaceCapableInterface.php';
require_once 'vendor/laminas/laminas-cache/src/Storage/Event.php';
require_once 'vendor/laminas/laminas-cache/src/Storage/PostEvent.php';
require_once 'vendor/laminas/laminas-cache/src/Storage/ExceptionEvent.php';
require_once 'vendor/laminas/laminas-cache/src/Exception/ExceptionInterface.php';
require_once 'vendor/laminas/laminas-cache/src/Exception/OutOfSpaceException.php';
require_once 'vendor/laminas/laminas-stdlib/src/ParameterObjectInterface.php';
require_once 'vendor/laminas/laminas-stdlib/src/AbstractOptions.php';
require_once 'vendor/laminas/laminas-cache/src/Storage/Adapter/AdapterOptions.php';
require_once 'vendor/laminas/laminas-cache-storage-adapter-memory/src/MemoryOptions.php';
require_once 'vendor/laminas/laminas-cache/src/Storage/Adapter/AbstractAdapter.php';
require_once 'vendor/laminas/laminas-cache-storage-adapter-memory/src/Memory.php';
// Autoload Assert (used by AbstractAdapter::addItem) to get its memory footprint addition out of the way
Assert::boolean(true);
$options = new MemoryOptions();
$storage = new Memory($options);
// AbstractAdapter::addItem triggers autoloading of Assert the first time it is called.
// Call it once here to trigger that initial autoloading and only adjust the memory limit afterward.
$storage->addItem('init', true);
$storage->flush();
$startMem = self::getMem();
printf('%d real mem at start' . PHP_EOL, $startMem);
// Write waste until all allocated but currently unused pages are filled
$bin = [];
$waste = 'The quick brown fox jumps over the lazy dog.';
while (self::getMem() === $startMem) {
$bin[] = $waste;
}
printf('%d real mem with unused pages filled' . PHP_EOL, self::getMem());
$options->setMemoryLimit(self::getMem() + self::MEM_STEPPING);
printf('%d = new real mem limit' . PHP_EOL, $options->getMemoryLimit());
try {
for ($i = 0; $i < self::LOOP_LIMIT; $i++) {
$storage->addItem('item' . $i, $waste);
}
throw new Exception('Filling cache with test data to reach memory limit failed, got to ' . self::getMem());
} catch (OutOfSpaceException $ignore) {
}
printf('%d real mem after exception' . PHP_EOL, self::getMem());
printf('%d mem (used pages only) after exception' . PHP_EOL, memory_get_usage());
$storage->flush();
unset($bin);
if (isset($loader)) {
$loader->unregister();
unset($loader);
ComposerAutoloaderInit52b4f69827d264f7b84d362d8e816c99::$loader = null;
}
gc_collect_cycles();
gc_mem_caches();
printf('%d mem (used pages only) after flush' . PHP_EOL, memory_get_usage());
printf('%d real mem after flush' . PHP_EOL, self::getMem());
memprof_dump_callgrind(fopen("callgrind.out", "w"));
$dump = memprof_dump_array();
file_put_contents('array.out', print_r($dump, true));
$storage->addItem('item' . $i, sha1((string) mt_rand()));
assert(self::getMem() < $options->getMemoryLimit());
}
private static function getMem(): int
{
// Real memory usage (same as in Memory methods getAvailableSpace and hasAvailableSpace)
return memory_get_usage(true);
}
}
$test = new MemoryTest();
$test->testReclaimMemory(); |
Regarding the Thats kinda the same approach as in |
Bug Report
The way the Memory adapter is currently implemented (using only
memory_get_usage(true)
as source of information), callingPsr\SimpleCache\CacheInterface::clear()
has no effect onhasAvailableSpace()
.Hence once a
CacheException
with previousOutOfSpaceException
has been thrown, even callingclear()
does not reclaim space as far as the Memory adapter is concerned. At this point the cache is permanently unusable because PHP won't ever reclaim allocated memory, only fetch more by 2 MiB increments.If this can't be fixed in some way, please at least document the limitation.
The text was updated successfully, but these errors were encountered: