From 3533ec3159161979fc8291feb1e60e3fa44c1f6c Mon Sep 17 00:00:00 2001 From: Sergey Biryukov Date: Wed, 22 Feb 2023 14:25:25 +0000 Subject: [PATCH] Media: Add `WP_Image_Editor_Imagick::set_imagick_time_limit()` method. This aims to avoid timeout in Imagick operations. Previously, Imagick operations could silently error by timeout and produce unexpected results. The new `::set_imagick_time_limit()` method, now used in `::resize()` and `::crop()`, will better handle garbage collection in these cases as well as better align Imagick's timeout with PHP timeout, assuming it is set. Props drzraf, audrasjb, costdev, antpb, SergeyBiryukov. Fixes #52569. Built from https://develop.svn.wordpress.org/trunk@55404 git-svn-id: http://core.svn.wordpress.org/trunk@54937 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- wp-admin/includes/class-wp-debug-data.php | 2 + wp-includes/class-wp-image-editor-imagick.php | 48 +++++++++++++++++++ wp-includes/version.php | 2 +- 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/wp-admin/includes/class-wp-debug-data.php b/wp-admin/includes/class-wp-debug-data.php index 5c3034ce39b..692fd0b5591 100644 --- a/wp-admin/includes/class-wp-debug-data.php +++ b/wp-admin/includes/class-wp-debug-data.php @@ -581,6 +581,7 @@ public static function debug_data() { 'map' => ( defined( 'imagick::RESOURCETYPE_MAP' ) ? size_format( $imagick->getResourceLimit( imagick::RESOURCETYPE_MAP ) ) : $not_available ), 'memory' => ( defined( 'imagick::RESOURCETYPE_MEMORY' ) ? size_format( $imagick->getResourceLimit( imagick::RESOURCETYPE_MEMORY ) ) : $not_available ), 'thread' => ( defined( 'imagick::RESOURCETYPE_THREAD' ) ? $imagick->getResourceLimit( imagick::RESOURCETYPE_THREAD ) : $not_available ), + 'time' => ( defined( 'imagick::RESOURCETYPE_TIME' ) ? $imagick->getResourceLimit( imagick::RESOURCETYPE_TIME ) : $not_available ), ); $limits_debug = array( @@ -590,6 +591,7 @@ public static function debug_data() { 'imagick::RESOURCETYPE_MAP' => ( defined( 'imagick::RESOURCETYPE_MAP' ) ? size_format( $imagick->getResourceLimit( imagick::RESOURCETYPE_MAP ) ) : 'not available' ), 'imagick::RESOURCETYPE_MEMORY' => ( defined( 'imagick::RESOURCETYPE_MEMORY' ) ? size_format( $imagick->getResourceLimit( imagick::RESOURCETYPE_MEMORY ) ) : 'not available' ), 'imagick::RESOURCETYPE_THREAD' => ( defined( 'imagick::RESOURCETYPE_THREAD' ) ? $imagick->getResourceLimit( imagick::RESOURCETYPE_THREAD ) : 'not available' ), + 'imagick::RESOURCETYPE_TIME' => ( defined( 'imagick::RESOURCETYPE_TIME' ) ? $imagick->getResourceLimit( imagick::RESOURCETYPE_TIME ) : 'not available' ), ); $info['wp-media']['fields']['imagick_limits'] = array( diff --git a/wp-includes/class-wp-image-editor-imagick.php b/wp-includes/class-wp-image-editor-imagick.php index 40e3cbb823f..69ab7b176c4 100644 --- a/wp-includes/class-wp-image-editor-imagick.php +++ b/wp-includes/class-wp-image-editor-imagick.php @@ -253,6 +253,50 @@ protected function update_size( $width = null, $height = null ) { return parent::update_size( $width, $height ); } + /** + * Sets Imagick time limit. + * + * Depending on configuration, Imagick processing may take time. + * + * Multiple problems exist if PHP times out before ImageMagick completed: + * 1. Temporary files aren't cleaned by ImageMagick garbage collection. + * 2. No clear error is provided. + * 3. The cause of such timeout can be hard to pinpoint. + * + * This function, which is expected to be run before heavy image routines, resolves + * point 1 above by aligning Imagick's timeout with PHP's timeout, assuming it is set. + * + * Note: + * - Imagick resource exhaustion does not issue catchable exceptions (yet). + * See https://github.com/Imagick/imagick/issues/333. + * - The resource limit is not saved/restored. It applies to subsequent + * image operations within the time of the HTTP request. + * + * @since 6.2.0 + * + * @return int|null The new limit on success, null on failure. + */ + public static function set_imagick_time_limit() { + if ( ! defined( 'Imagick::RESOURCETYPE_TIME' ) ) { + return null; + } + + // Returns PHP_FLOAT_MAX if unset. + $imagick_timeout = Imagick::getResourceLimit( Imagick::RESOURCETYPE_TIME ); + + // Convert to an integer, keeping in mind that: 0 === (int) PHP_FLOAT_MAX. + $imagick_timeout = $imagick_timeout > PHP_INT_MAX ? PHP_INT_MAX : (int) $imagick_timeout; + + $php_timeout = (int) ini_get( 'max_execution_time' ); + + if ( $php_timeout > 1 && $php_timeout < $imagick_timeout ) { + $limit = (float) 0.8 * $php_timeout; + Imagick::setResourceLimit( Imagick::RESOURCETYPE_TIME, $limit ); + + return $limit; + } + } + /** * Resizes current image. * @@ -283,6 +327,8 @@ public function resize( $max_w, $max_h, $crop = false ) { return $this->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h ); } + self::set_imagick_time_limit(); + // Execute the resize. $thumb_result = $this->thumbnail_image( $dst_w, $dst_h ); if ( is_wp_error( $thumb_result ) ) { @@ -549,6 +595,8 @@ public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = nu $src_h -= $src_y; } + self::set_imagick_time_limit(); + try { $this->image->cropImage( $src_w, $src_h, $src_x, $src_y ); $this->image->setImagePage( $src_w, $src_h, 0, 0 ); diff --git a/wp-includes/version.php b/wp-includes/version.php index a8ee8af1bd3..3aa8efcf29c 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -16,7 +16,7 @@ * * @global string $wp_version */ -$wp_version = '6.2-beta3-55402'; +$wp_version = '6.2-beta3-55404'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.