diff --git a/src/wp-admin/includes/class-wp-debug-data.php b/src/wp-admin/includes/class-wp-debug-data.php index 5c3034ce39b63..692fd0b559136 100644 --- a/src/wp-admin/includes/class-wp-debug-data.php +++ b/src/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/src/wp-includes/class-wp-image-editor-imagick.php b/src/wp-includes/class-wp-image-editor-imagick.php index 40e3cbb823fec..69ab7b176c41b 100644 --- a/src/wp-includes/class-wp-image-editor-imagick.php +++ b/src/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 );