From aa561e67a1da534220af531d5e66872757e35c15 Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Fri, 8 Jul 2016 14:37:30 +0000 Subject: [PATCH] Bootstrap: Enhance core's memory limit handling. * Don't lower memory limit if the current limit is greater than `WP_MAX_MEMORY_LIMIT`. * Set `WP_MEMORY_LIMIT` and `WP_MAX_MEMORY_LIMIT` to current limit if the `memory_limit` setting can't be changed at runtime. * Use `wp_convert_hr_to_bytes()` when parsing the value of the `memory_limit` setting because it can be a shorthand or an integer value. * Introduce `wp_raise_memory_limit( $context )` to raise the PHP memory limit for memory intensive processes. This DRYs up some logic and includes the existing `admin_memory_limit` and `image_memory_limit` filters. The function can also be used for custom contexts, the `{$context}_memory_limit` filter allows to customize the limit. * Introduce `wp_is_ini_value_changeable( $setting )` to determine whether a PHP ini value is changeable at runtime. * Remove a `function_exists( 'memory_get_usage' )` check. Since PHP 5.2.1 support for memory limit is always enabled. Related commits: [38011-38013] Props jrf, A5hleyRich, swissspidy, ocean90. Fixes #32075. Built from https://develop.svn.wordpress.org/trunk@38015 git-svn-id: http://core.svn.wordpress.org/trunk@37956 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- wp-admin/admin.php | 16 +-- wp-admin/includes/file.php | 5 +- wp-admin/includes/image-edit.php | 3 +- wp-includes/class-wp-image-editor-gd.php | 12 +- wp-includes/class-wp-image-editor-imagick.php | 5 +- wp-includes/default-constants.php | 43 +++---- wp-includes/deprecated.php | 5 +- wp-includes/functions.php | 109 ++++++++++++++++++ wp-includes/load.php | 24 ++++ wp-includes/version.php | 2 +- 10 files changed, 164 insertions(+), 60 deletions(-) diff --git a/wp-admin/admin.php b/wp-admin/admin.php index 3fabd8751e..ad341c8880 100644 --- a/wp-admin/admin.php +++ b/wp-admin/admin.php @@ -138,21 +138,7 @@ else require(ABSPATH . 'wp-admin/menu.php'); if ( current_user_can( 'manage_options' ) ) { - /** - * Filters the maximum memory limit available for administration screens. - * - * This only applies to administrators, who may require more memory for tasks like updates. - * Memory limits when processing images (uploaded or edited by users of any role) are - * handled separately. - * - * The WP_MAX_MEMORY_LIMIT constant specifically defines the maximum memory limit available - * when in the administration back end. The default is 256M, or 256 megabytes of memory. - * - * @since 3.0.0 - * - * @param string 'WP_MAX_MEMORY_LIMIT' The maximum WordPress memory limit. Default 256M. - */ - @ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) ); + wp_raise_memory_limit( 'admin' ); } /** diff --git a/wp-admin/includes/file.php b/wp-admin/includes/file.php index 3b754dfd6f..ad66a8d0a8 100644 --- a/wp-admin/includes/file.php +++ b/wp-admin/includes/file.php @@ -569,9 +569,8 @@ function unzip_file($file, $to) { if ( ! $wp_filesystem || !is_object($wp_filesystem) ) return new WP_Error('fs_unavailable', __('Could not access filesystem.')); - // Unzip can use a lot of memory, but not this much hopefully - /** This filter is documented in wp-admin/admin.php */ - @ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) ); + // Unzip can use a lot of memory, but not this much hopefully. + wp_raise_memory_limit( 'admin' ); $needed_dirs = array(); $to = trailingslashit($to); diff --git a/wp-admin/includes/image-edit.php b/wp-admin/includes/image-edit.php index b34d2ac702..c614a7793e 100644 --- a/wp-admin/includes/image-edit.php +++ b/wp-admin/includes/image-edit.php @@ -585,8 +585,7 @@ function image_edit_apply_changes( $image, $changes ) { function stream_preview_image( $post_id ) { $post = get_post( $post_id ); - /** This filter is documented in wp-admin/admin.php */ - @ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) ); + wp_raise_memory_limit( 'admin' ); $img = wp_get_image_editor( _load_image_to_edit_path( $post_id ) ); diff --git a/wp-includes/class-wp-image-editor-gd.php b/wp-includes/class-wp-image-editor-gd.php index 96e7bf1dba..d7823ebbcf 100644 --- a/wp-includes/class-wp-image-editor-gd.php +++ b/wp-includes/class-wp-image-editor-gd.php @@ -96,18 +96,8 @@ class WP_Image_Editor_GD extends WP_Image_Editor { if ( ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) ) return new WP_Error( 'error_loading_image', __('File doesn’t exist?'), $this->file ); - /** - * Filters the memory limit allocated for image manipulation. - * - * @since 3.5.0 - * - * @param int|string $limit Maximum memory limit to allocate for images. Default WP_MAX_MEMORY_LIMIT. - * Accepts an integer (bytes), or a shorthand string notation, such as '256M'. - */ - $image_memory_limit = apply_filters( 'image_memory_limit', WP_MAX_MEMORY_LIMIT ); - // Set artificially high because GD uses uncompressed images in memory. - @ini_set( 'memory_limit', $image_memory_limit ); + wp_raise_memory_limit( 'image' ); $this->image = @imagecreatefromstring( file_get_contents( $this->file ) ); diff --git a/wp-includes/class-wp-image-editor-imagick.php b/wp-includes/class-wp-image-editor-imagick.php index 3d92d39096..82b872d732 100644 --- a/wp-includes/class-wp-image-editor-imagick.php +++ b/wp-includes/class-wp-image-editor-imagick.php @@ -137,14 +137,11 @@ class WP_Image_Editor_Imagick extends WP_Image_Editor { if ( ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) ) return new WP_Error( 'error_loading_image', __('File doesn’t exist?'), $this->file ); - /** This filter is documented in wp-includes/class-wp-image-editor-gd.php */ - $image_memory_limit = apply_filters( 'image_memory_limit', WP_MAX_MEMORY_LIMIT ); - /* * Even though Imagick uses less PHP memory than GD, set higher limit * for users that have low PHP.ini limits. */ - @ini_set( 'memory_limit', $image_memory_limit ); + wp_raise_memory_limit( 'image' ); try { $this->image = new Imagick( $this->file ); diff --git a/wp-includes/default-constants.php b/wp-includes/default-constants.php index 1a80e2a6ab..2aa3430ec5 100644 --- a/wp-includes/default-constants.php +++ b/wp-includes/default-constants.php @@ -28,36 +28,39 @@ function wp_initial_constants() { define( 'TB_IN_BYTES', 1024 * GB_IN_BYTES ); /**#@-*/ - // set memory limits - if ( !defined('WP_MEMORY_LIMIT') ) { - if ( is_multisite() ) { - define('WP_MEMORY_LIMIT', '64M'); + $current_limit = @ini_get( 'memory_limit' ); + $current_limit_int = wp_convert_hr_to_bytes( $current_limit ); + + // Define memory limits. + if ( ! defined( 'WP_MEMORY_LIMIT' ) ) { + if ( false === wp_is_ini_value_changeable( 'memory_limit' ) ) { + define( 'WP_MEMORY_LIMIT', $current_limit ); + } elseif ( is_multisite() ) { + define( 'WP_MEMORY_LIMIT', '64M' ); } else { - define('WP_MEMORY_LIMIT', '40M'); + define( 'WP_MEMORY_LIMIT', '40M' ); } } if ( ! defined( 'WP_MAX_MEMORY_LIMIT' ) ) { - define( 'WP_MAX_MEMORY_LIMIT', '256M' ); + if ( false === wp_is_ini_value_changeable( 'memory_limit' ) ) { + define( 'WP_MAX_MEMORY_LIMIT', $current_limit ); + } elseif ( -1 === $current_limit_int || $current_limit_int > 268435456 /* = 256M */ ) { + define( 'WP_MAX_MEMORY_LIMIT', $current_limit ); + } else { + define( 'WP_MAX_MEMORY_LIMIT', '256M' ); + } + } + + // Set memory limits. + $wp_limit_int = wp_convert_hr_to_bytes( WP_MEMORY_LIMIT ); + if ( -1 !== $current_limit_int && ( -1 === $wp_limit_int || $wp_limit_int > $current_limit_int ) ) { + @ini_set( 'memory_limit', WP_MEMORY_LIMIT ); } if ( ! isset($blog_id) ) $blog_id = 1; - // set memory limits. - if ( function_exists( 'memory_get_usage' ) ) { - $current_limit = @ini_get( 'memory_limit' ); - $current_limit_int = intval( $current_limit ); - if ( false !== strpos( $current_limit, 'G' ) ) - $current_limit_int *= 1024; - $wp_limit_int = intval( WP_MEMORY_LIMIT ); - if ( false !== strpos( WP_MEMORY_LIMIT, 'G' ) ) - $wp_limit_int *= 1024; - - if ( -1 != $current_limit && ( -1 == WP_MEMORY_LIMIT || $current_limit_int < $wp_limit_int ) ) - @ini_set( 'memory_limit', WP_MEMORY_LIMIT ); - } - if ( !defined('WP_CONTENT_DIR') ) define( 'WP_CONTENT_DIR', ABSPATH . 'wp-content' ); // no trailing slash, full paths only - WP_CONTENT_URL is defined further down diff --git a/wp-includes/deprecated.php b/wp-includes/deprecated.php index c61e5d30b8..0d30113b28 100644 --- a/wp-includes/deprecated.php +++ b/wp-includes/deprecated.php @@ -3175,11 +3175,8 @@ function wp_load_image( $file ) { if ( ! function_exists('imagecreatefromstring') ) return __('The GD image library is not installed.'); - /** This filter is documented in wp-includes/class-wp-image-editor-gd.php */ - $image_memory_limit = apply_filters( 'image_memory_limit', WP_MAX_MEMORY_LIMIT ); - // Set artificially high because GD uses uncompressed images in memory. - @ini_set( 'memory_limit', $image_memory_limit ); + wp_raise_memory_limit( 'image' ); $image = imagecreatefromstring( file_get_contents( $file ) ); diff --git a/wp-includes/functions.php b/wp-includes/functions.php index df0dbce676..3159e8c460 100644 --- a/wp-includes/functions.php +++ b/wp-includes/functions.php @@ -5379,3 +5379,112 @@ function wp_object_type_exists( $object_type ) { return false; } + +/** + * Attempts to raise the PHP memory limit for memory intensive processes. + * + * Only allows raising the existing limit and prevents lowering it. + * + * @since 4.6.0 + * + * @param string $context Context in which the function is called. + * Either 'admin', 'image' or an arbitrary other context. + * Defaults to 'admin'. + * If an arbitrary context is passed, the similarly arbitrary + * "{$context}_memory_limit" filter will be invoked. + * @return bool|int|string The limit that was set or false on failure. + */ +function wp_raise_memory_limit( $context = 'admin' ) { + // Exit early if the limit cannot be changed. + if ( false === wp_is_ini_value_changeable( 'memory_limit' ) ) { + return false; + } + + $current_limit = @ini_get( 'memory_limit' ); + $current_limit_int = wp_convert_hr_to_bytes( $current_limit ); + + if ( -1 === $current_limit_int ) { + return false; + } + + $wp_max_limit = WP_MAX_MEMORY_LIMIT; + $wp_max_limit_int = wp_convert_hr_to_bytes( $wp_max_limit ); + $filtered_limit = $wp_max_limit; + + switch ( $context ) { + case 'admin': + /** + * Filters the maximum memory limit available for administration screens. + * + * This only applies to administrators, who may require more memory for tasks like updates. + * Memory limits when processing images (uploaded or edited by users of any role) are + * handled separately. + * + * The WP_MAX_MEMORY_LIMIT constant specifically defines the maximum memory limit available + * when in the administration back end. The default is 256M (256 megabytes + * of memory) or the original `memory_limit` php.ini value if this is higher. + * + * @since 3.0.0 + * @since 4.6.0 The default takes the original `memory_limit` into account. + * + * @param int|string $filtered_limit The maximum WordPress memory limit. + * Accepts an integer (bytes), or a shorthand string + * notation, such as '256M'. + */ + $filtered_limit = apply_filters( 'admin_memory_limit', $filtered_limit ); + break; + + case 'image': + /** + * Filters the memory limit allocated for image manipulation. + * + * @since 3.5.0 + * @since 4.6.0 The default takes the original `memory_limit` into account. + * + * @param int|string $filtered_limit Maximum memory limit to allocate for images. + * Default WP_MAX_MEMORY_LIMIT or the original + * php.ini memory_limit, whichever is higher. + * Accepts an integer (bytes), or a shorthand string + * notation, such as '256M'. + */ + $filtered_limit = apply_filters( 'image_memory_limit', $filtered_limit ); + break; + + default: + /** + * Filters the memory limit allocated for arbitrary contexts. + * + * The dynamic portion of the hook name, `$context`, refers to an arbitrary + * context passed on calling the function. This allows for plugins to define + * their own contexts for raising the memory limit. + * + * @since 4.6.0 + * + * @param int|string $filtered_limit Maximum memory limit to allocate for images. + * Default 256M or the original php.ini memory_limit, + * whichever is higher. + * Accepts an integer (bytes), or a shorthand string + * notation, such as '256M'. + */ + $filtered_limit = apply_filters( "{$context}_memory_limit", $filtered_limit ); + break; + } + + $filtered_limit_int = wp_convert_hr_to_bytes( $filtered_limit ); + + if ( -1 === $filtered_limit_int || ( $filtered_limit_int > $wp_max_limit_int && $filtered_limit_int > $current_limit_int ) ) { + if ( false !== @ini_set( 'memory_limit', $filtered_limit ) ) { + return $filtered_limit; + } else { + return false; + } + } elseif ( -1 === $wp_max_limit_int || $wp_max_limit_int > $current_limit_int ) { + if ( false !== @ini_set( 'memory_limit', $wp_max_limit ) ) { + return $wp_max_limit; + } else { + return false; + } + } + + return false; +} diff --git a/wp-includes/load.php b/wp-includes/load.php index ebaaa353a0..3b79351b97 100644 --- a/wp-includes/load.php +++ b/wp-includes/load.php @@ -1002,3 +1002,27 @@ function wp_convert_hr_to_bytes( $value ) { // Deal with large (float) values which run into the maximum integer size. return min( $bytes, PHP_INT_MAX ); } + +/** + * Determines whether a PHP ini value is changeable at runtime. + * + * @since 4.6.0 + * + * @link http://php.net/manual/en/function.ini-get-all.php + * + * @param string $setting The name of the ini setting to check. + * @return bool True if the value is changeable at runtime. False otherwise. + */ +function wp_is_ini_value_changeable( $setting ) { + static $ini_all; + + if ( ! isset( $ini_all ) ) { + $ini_all = ini_get_all(); + } + + if ( isset( $ini_all[ $setting ]['access'] ) && ( INI_ALL === $ini_all[ $setting ]['access'] || INI_USER === $ini_all[ $setting ]['access'] ) ) { + return true; + } + + return false; +} diff --git a/wp-includes/version.php b/wp-includes/version.php index acc2c57807..8bff31cb19 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -4,7 +4,7 @@ * * @global string $wp_version */ -$wp_version = '4.6-beta2-38014'; +$wp_version = '4.6-beta2-38015'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.