Skip to content

Commit

Permalink
Media: Add WP_Image_Editor_Imagick::set_imagick_time_limit() method.
Browse files Browse the repository at this point in the history
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.

git-svn-id: https://develop.svn.wordpress.org/trunk@55404 602fd350-edb4-49c9-b593-d223f7449a82
  • Loading branch information
SergeyBiryukov committed Feb 22, 2023
1 parent 552178a commit 01c65c0
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/wp-admin/includes/class-wp-debug-data.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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(
Expand Down
48 changes: 48 additions & 0 deletions src/wp-includes/class-wp-image-editor-imagick.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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 ) ) {
Expand Down Expand Up @@ -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 );
Expand Down

0 comments on commit 01c65c0

Please sign in to comment.