Clear thumbnail handles as soon as possible
authorTim Düsterhus <duesterhus@woltlab.com>
Wed, 12 Aug 2020 13:55:02 +0000 (15:55 +0200)
committerTim Düsterhus <duesterhus@woltlab.com>
Wed, 12 Aug 2020 13:57:48 +0000 (15:57 +0200)
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}

wcfsetup/install/files/lib/acp/form/StyleAddForm.class.php
wcfsetup/install/files/lib/data/style/StyleAction.class.php
wcfsetup/install/files/lib/system/upload/DefaultUploadFileSaveStrategy.class.php
wcfsetup/install/files/lib/system/worker/UserRebuildDataWorker.class.php
wcfsetup/install/files/lib/util/ImageUtil.class.php

index 7f1370c88a85f35499c8fa407328e50c769fa324..445878a4008cc7ab808c88dcb642a36cfeb481ce 100644 (file)
@@ -537,6 +537,8 @@ class StyleAddForm extends AbstractForm {
                                                        $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
index 0d7915767ad8e4729051d98467cde7cf43e1c01d..4bf7800b3a16774518080c3ee3d49b02d0333e58 100644 (file)
@@ -334,6 +334,8 @@ class StyleAction extends AbstractDatabaseObjectAction implements IToggleAction
                                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.
index 644a135b9f84799a5b27fe6452278b610a2934db..3aa6a891e064eb5b0d7cdaed28eb3850219db8e9 100644 (file)
@@ -322,6 +322,9 @@ class DefaultUploadFileSaveStrategy implements IUploadFileSaveStrategy {
                        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);
index 009fb72a184f55747676c766487f85aa770b695c..6985f891dc11fc85b793dbcffd358b853f44ff6a 100644 (file)
@@ -205,6 +205,8 @@ class UserRebuildDataWorker extends AbstractRebuildDataWorker {
                                        
                                        $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) {
index 6c5cb32f7e710ccf9d2a7ba8925f8e666d32df57..9f274c050d7e5f60a572f88e861903e1c2bfbfab 100644 (file)
@@ -115,6 +115,9 @@ final class ImageUtil {
                        $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;