This reproduces when rebuilding attachment thumbnails for largish animated
GIF files using ImageMagick. The ImageMagick on-disk cache quota is not
sufficient to hold:
1) The original
2) The tiny thumbnail
3) The in-progress regular thumbnail
The old value of the `$thumbnail` variable will only be destructed once (3)
returns. But the memory is already needed during execution of (3).
So this commit adjusts the code to `null` out the `$thumbnail` variable as
soon as possible, instead of waiting until it goes out of scope naturally.
Example stack trace from the worker:
ImagickException: cache resources exhausted `/var/www/html/attachments/00/1-*snip*.bin' @ error/cache.c/OpenPixelCache/4083 in /var/www/html/lib/system/image/adapter/ImagickImageAdapter.class.php:132
Stack trace:
#0 /var/www/html/lib/system/image/adapter/ImagickImageAdapter.class.php(132): Imagick->cropthumbnailimage(352, 198)
#1 /var/www/html/lib/system/image/adapter/ImageAdapter.class.php(82): wcf\system\image\adapter\ImagickImageAdapter->createThumbnail(352, 198, 0)
#2 /var/www/html/lib/system/upload/DefaultUploadFileSaveStrategy.class.php(323): wcf\system\image\adapter\ImageAdapter->createThumbnail(352, 198, 0)
#3 /var/www/html/lib/data/attachment/AttachmentAction.class.php(226): wcf\system\upload\DefaultUploadFileSaveStrategy->generateThumbnails(Object(wcf\data\attachment\Attachment))
#4 /var/www/html/lib/data/AbstractDatabaseObjectAction.class.php(204): wcf\data\attachment\AttachmentAction->generateThumbnails()
#5 /var/www/html/lib/system/worker/AttachmentRebuildDataWorker.class.php(48): wcf\data\AbstractDatabaseObjectAction->executeAction()
#6 /var/www/html/lib/system/cli/command/WorkerCLICommand.class.php(152): wcf\system\worker\AttachmentRebuildDataWorker->execute()
#7 /var/www/html/lib/system/CLIWCF.class.php(291): wcf\system\cli\command\WorkerCLICommand->execute(Array)
#8 /var/www/html/lib/system/CLIWCF.class.php(85): wcf\system\CLIWCF->initCommands()
#9 /var/www/html/cli.php(18): wcf\system\CLIWCF->__construct()
#10 {main}
$options['size']['preserveAspectRatio'] ?? true
);
$adapter->writeImage($thumbnail, $fileLocation);
+ // Clear thumbnail as soon as possible to free up the memory.
+ $thumbnail = null;
}
// Check again after scaling
foreach ($images as $filename => $length) {
$thumbnail = $adapter->createThumbnail($length, $length);
$adapter->writeImage($thumbnail, $style->getAssetPath().$filename);
+ // Clear thumbnail as soon as possible to free up the memory.
+ $thumbnail = null;
}
// Create ICO file.
if ($file->width > $sizeData['width'] || $file->height > $sizeData['height']) {
$thumbnail = $adapter->createThumbnail($sizeData['width'], $sizeData['height'], isset($sizeData['retainDimensions']) ? $sizeData['retainDimensions'] : true);
$adapter->writeImage($thumbnail, $thumbnailLocation);
+ // Clear thumbnail as soon as possible to free up the memory for the next size.
+ $thumbnail = null;
+
if (file_exists($thumbnailLocation) && ($imageData = @getimagesize($thumbnailLocation)) !== false) {
$updateData[$prefix.'Type'] = $imageData['mime'];
$updateData[$prefix.'Size'] = @filesize($thumbnailLocation);
$thumbnail = $adapter->createThumbnail($width, $height, false);
$adapter->writeImage($thumbnail, $avatar->getLocation());
+ // Clear thumbnail as soon as possible to free up the memory.
+ $thumbnail = null;
}
if ($width != UserAvatar::AVATAR_SIZE || $height != UserAvatar::AVATAR_SIZE) {
$filename = FileUtil::getTemporaryFilename();
$thumbnail = $adapter->createThumbnail($maxWidth, $maxHeight, $obtainDimensions);
$adapter->writeImage($thumbnail, $filename);
+ // Clear thumbnail as soon as possible to free up the memory.
+ // This is technically useless, but done for consistency.
+ $thumbnail = null;
}
return $filename;