Description
Bug Report
Q | A |
---|---|
Version(s) | 2.4.1 |
Summary
In our application multiple cron jobs running at the same time.
Circa every two hours (sporadically) we get an exception: Serialized data must be a string containing serialized PHP code; received:
Current behavior
The exception occurs because Laminas\Cache\Storage\Adapter\Filesystem::internalGetItem
returns an empty string which cannot be deserialized by laminas/laminas-serializer/src/Adapter/PhpSerialize
later on.
It turned out that cache files are created before filled and there is a short time slot where the file content is empty.
(the time slot is in Laminas\Cache\Storage\Adapter\Filesystem\LocalFilesystemInteraction::write
after fopen
and before flock
)
Some micro- or milliseconds later the file is filled.
How to reproduce
- Setup an application with laminas filesystem cache. The plugin
serializer
is enabled. - Add a debug break point to
Laminas\Cache\Storage\Adapter\Filesystem\LocalFilesystemInteraction::write
beforeflock
is called the first time and afterchmod
is called (line 109 in 2.4.1). - Run into the break point with the debugger and keep the execution paused.
- Disable debugger listening to the next request and ensure that the next request will not be blocked by the paused debug session.
- Perform an asynchronous call that requests the same cache entry from filesystem cache.
- Exception
Serialized data must be a string containing serialized PHP code; received:
should occur
I don't know if it would be possible to cover this case with a unit test (would require two asynchronous calls and a delay in chmod
operation?)
Expected behavior
Laminas\Cache\Storage\Adapter\Filesystem::internalGetItem
will always return with $success = false;
or with the file content and never with empty content as a consequence of the file beeing created but the content not written yet (by Laminas\Cache\Storage\Adapter\Filesystem\LocalFilesystemInteraction::write
).
Notes
- The error would not occur on our system with the following workaround in
Laminas\Cache\Storage\Adapter\Filesystem::internalGetItem
before$success = true;
is called:
$maxIterations = 20; //max 20ms
$currentIteration = 0;
while ($data === '' && $currentIteration < $maxIterations) {
usleep(1000); //1ms
if (! $this->internalHasItem($normalizedKey)) {
$success = false;
return;
}
$data = $this->getFileContent($filespec);
if ($data !== '') {
return $data;
}
$currentIteration++;
}
- Maybe there is a more reliable way to ensure that the created file will be filled when requested.
- Maybe it would be possible to just return with
$success = false;
if the file content is empty and theserialize
plugin is used (I don't know if an empty string is valid, if theserialize
plugin is not used). However, without theserialize
plugin an empty string could still be the result of a read/write bug.