Skip to content

Commit 01c65c0

Browse files
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. git-svn-id: https://develop.svn.wordpress.org/trunk@55404 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 552178a commit 01c65c0

File tree

2 files changed

+50
-0
lines changed

2 files changed

+50
-0
lines changed

src/wp-admin/includes/class-wp-debug-data.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,7 @@ public static function debug_data() {
581581
'map' => ( defined( 'imagick::RESOURCETYPE_MAP' ) ? size_format( $imagick->getResourceLimit( imagick::RESOURCETYPE_MAP ) ) : $not_available ),
582582
'memory' => ( defined( 'imagick::RESOURCETYPE_MEMORY' ) ? size_format( $imagick->getResourceLimit( imagick::RESOURCETYPE_MEMORY ) ) : $not_available ),
583583
'thread' => ( defined( 'imagick::RESOURCETYPE_THREAD' ) ? $imagick->getResourceLimit( imagick::RESOURCETYPE_THREAD ) : $not_available ),
584+
'time' => ( defined( 'imagick::RESOURCETYPE_TIME' ) ? $imagick->getResourceLimit( imagick::RESOURCETYPE_TIME ) : $not_available ),
584585
);
585586

586587
$limits_debug = array(
@@ -590,6 +591,7 @@ public static function debug_data() {
590591
'imagick::RESOURCETYPE_MAP' => ( defined( 'imagick::RESOURCETYPE_MAP' ) ? size_format( $imagick->getResourceLimit( imagick::RESOURCETYPE_MAP ) ) : 'not available' ),
591592
'imagick::RESOURCETYPE_MEMORY' => ( defined( 'imagick::RESOURCETYPE_MEMORY' ) ? size_format( $imagick->getResourceLimit( imagick::RESOURCETYPE_MEMORY ) ) : 'not available' ),
592593
'imagick::RESOURCETYPE_THREAD' => ( defined( 'imagick::RESOURCETYPE_THREAD' ) ? $imagick->getResourceLimit( imagick::RESOURCETYPE_THREAD ) : 'not available' ),
594+
'imagick::RESOURCETYPE_TIME' => ( defined( 'imagick::RESOURCETYPE_TIME' ) ? $imagick->getResourceLimit( imagick::RESOURCETYPE_TIME ) : 'not available' ),
593595
);
594596

595597
$info['wp-media']['fields']['imagick_limits'] = array(

src/wp-includes/class-wp-image-editor-imagick.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,50 @@ protected function update_size( $width = null, $height = null ) {
253253
return parent::update_size( $width, $height );
254254
}
255255

256+
/**
257+
* Sets Imagick time limit.
258+
*
259+
* Depending on configuration, Imagick processing may take time.
260+
*
261+
* Multiple problems exist if PHP times out before ImageMagick completed:
262+
* 1. Temporary files aren't cleaned by ImageMagick garbage collection.
263+
* 2. No clear error is provided.
264+
* 3. The cause of such timeout can be hard to pinpoint.
265+
*
266+
* This function, which is expected to be run before heavy image routines, resolves
267+
* point 1 above by aligning Imagick's timeout with PHP's timeout, assuming it is set.
268+
*
269+
* Note:
270+
* - Imagick resource exhaustion does not issue catchable exceptions (yet).
271+
* See https://github.com/Imagick/imagick/issues/333.
272+
* - The resource limit is not saved/restored. It applies to subsequent
273+
* image operations within the time of the HTTP request.
274+
*
275+
* @since 6.2.0
276+
*
277+
* @return int|null The new limit on success, null on failure.
278+
*/
279+
public static function set_imagick_time_limit() {
280+
if ( ! defined( 'Imagick::RESOURCETYPE_TIME' ) ) {
281+
return null;
282+
}
283+
284+
// Returns PHP_FLOAT_MAX if unset.
285+
$imagick_timeout = Imagick::getResourceLimit( Imagick::RESOURCETYPE_TIME );
286+
287+
// Convert to an integer, keeping in mind that: 0 === (int) PHP_FLOAT_MAX.
288+
$imagick_timeout = $imagick_timeout > PHP_INT_MAX ? PHP_INT_MAX : (int) $imagick_timeout;
289+
290+
$php_timeout = (int) ini_get( 'max_execution_time' );
291+
292+
if ( $php_timeout > 1 && $php_timeout < $imagick_timeout ) {
293+
$limit = (float) 0.8 * $php_timeout;
294+
Imagick::setResourceLimit( Imagick::RESOURCETYPE_TIME, $limit );
295+
296+
return $limit;
297+
}
298+
}
299+
256300
/**
257301
* Resizes current image.
258302
*
@@ -283,6 +327,8 @@ public function resize( $max_w, $max_h, $crop = false ) {
283327
return $this->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h );
284328
}
285329

330+
self::set_imagick_time_limit();
331+
286332
// Execute the resize.
287333
$thumb_result = $this->thumbnail_image( $dst_w, $dst_h );
288334
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
549595
$src_h -= $src_y;
550596
}
551597

598+
self::set_imagick_time_limit();
599+
552600
try {
553601
$this->image->cropImage( $src_w, $src_h, $src_x, $src_y );
554602
$this->image->setImagePage( $src_w, $src_h, 0, 0 );

0 commit comments

Comments
 (0)