Core Upgrader: Add a locking mechanism to avoid two concurrent updates of WordPress occuring.

Fixes #34878

Built from https://develop.svn.wordpress.org/trunk@36349


git-svn-id: http://core.svn.wordpress.org/trunk@36316 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Dion Hulse 2016-01-19 05:07:26 +00:00
parent 60a9f41315
commit 006ae74d4c
3 changed files with 89 additions and 28 deletions

View File

@ -751,6 +751,64 @@ class WP_Upgrader {
}
}
/**
* Create a Lock using WordPress options.
*
* @since 4.5.0
* @access public
*
* @param string $lock_name The name of this unique lock.
* @param int $release_timeout The duration in seconds to respect an existing lock. Default: 1 hour.
* @return bool
*/
public function create_lock( $lock_name, $release_timeout = null ) {
global $wpdb;
if ( ! $release_timeout ) {
$release_timeout = HOUR_IN_SECONDS;
}
$lock_option = $lock_name . '.lock';
// Try to lock
$lock_result = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `$wpdb->options` ( `option_name`, `option_value`, `autoload` ) VALUES (%s, %s, 'no') /* LOCK */", $lock_option, time() ) );
if ( ! $lock_result ) {
$lock_result = get_option( $lock_option );
// If we couldn't create a lock, and there isn't a lock, bail
if ( ! $lock_result ) {
return false;
}
// Check to see if the lock is still valid
if ( $lock_result > ( time() - $release_timeout ) ) {
return false;
}
// There must exist an expired lock, clear it and re-gain it.
$this->release_lock( $lock_name );
return $this->create_lock( $lock_name, $release_timeout );
}
// Update the lock, as by this point we've definitely got a lock, just need to fire the actions
update_option( $lock_option, time() );
return true;
}
/**
* Release a lock created by `WP_Upgrader::create_lock()`.
*
* @since 4.5.0
* @access public
*
* @param string $lock_name The name of this unique lock.
* @return bool
*/
public function release_lock( $lock_name ) {
return delete_option( $lock_name . '.lock' );
}
}
/**
@ -2168,6 +2226,7 @@ class Core_Upgrader extends WP_Upgrader {
*/
public function upgrade_strings() {
$this->strings['up_to_date'] = __('WordPress is at the latest version.');
$this->strings['locked'] = __('Another update is currently in progress.');
$this->strings['no_package'] = __('Update package not available.');
$this->strings['downloading_package'] = __('Downloading update from <span class="code">%s</span>&#8230;');
$this->strings['unpack_package'] = __('Unpacking the update&#8230;');
@ -2252,25 +2311,38 @@ class Core_Upgrader extends WP_Upgrader {
else
$to_download = 'full';
// Lock to prevent multiple Core Updates occuring
$lock = $this->create_lock( 'core_updater', 15 * MINUTE_IN_SECONDS );
if ( ! $lock ) {
return new WP_Error( 'locked', $this->strings['locked'] );
}
$download = $this->download_package( $current->packages->$to_download );
if ( is_wp_error($download) )
if ( is_wp_error( $download ) ) {
$this->release_lock( 'core_updater' );
return $download;
}
$working_dir = $this->unpack_package( $download );
if ( is_wp_error($working_dir) )
if ( is_wp_error( $working_dir ) ) {
$this->release_lock( 'core_updater' );
return $working_dir;
}
// Copy update-core.php from the new version into place.
if ( !$wp_filesystem->copy($working_dir . '/wordpress/wp-admin/includes/update-core.php', $wp_dir . 'wp-admin/includes/update-core.php', true) ) {
$wp_filesystem->delete($working_dir, true);
$this->release_lock( 'core_updater' );
return new WP_Error( 'copy_failed_for_update_core_file', __( 'The update cannot be installed because we will be unable to copy some files. This is usually due to inconsistent file permissions.' ), 'wp-admin/includes/update-core.php' );
}
$wp_filesystem->chmod($wp_dir . 'wp-admin/includes/update-core.php', FS_CHMOD_FILE);
require_once( ABSPATH . 'wp-admin/includes/update-core.php' );
if ( ! function_exists( 'update_core' ) )
if ( ! function_exists( 'update_core' ) ) {
$this->release_lock( 'core_updater' );
return new WP_Error( 'copy_failed_space', $this->strings['copy_failed_space'] );
}
$result = update_core( $working_dir, $wp_dir );
@ -2345,6 +2417,8 @@ class Core_Upgrader extends WP_Upgrader {
wp_version_check( $stats );
}
$this->release_lock( 'core_updater' );
return $result;
}
@ -2938,8 +3012,13 @@ class WP_Automatic_Updater {
$upgrade_result = new WP_Error( 'fs_unavailable', __( 'Could not access filesystem.' ) );
}
// Core doesn't output this, so let's append it so we don't get confused.
if ( 'core' == $type ) {
if ( is_wp_error( $upgrade_result ) && ( 'up_to_date' == $upgrade_result->get_error_code() || 'locked' == $upgrade_result->get_error_code() ) ) {
// These aren't actual errors, treat it as a skipped-update instead to avoid triggering the post-core update failure routines.
return false;
}
// Core doesn't output this, so let's append it so we don't get confused.
if ( is_wp_error( $upgrade_result ) ) {
$skin->error( __( 'Installation Failed' ), $upgrade_result );
} else {
@ -2975,25 +3054,8 @@ class WP_Automatic_Updater {
if ( ! is_main_network() || ! is_main_site() )
return;
$lock_name = 'auto_updater.lock';
// Try to lock
$lock_result = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `$wpdb->options` ( `option_name`, `option_value`, `autoload` ) VALUES (%s, %s, 'no') /* LOCK */", $lock_name, time() ) );
if ( ! $lock_result ) {
$lock_result = get_option( $lock_name );
// If we couldn't create a lock, and there isn't a lock, bail
if ( ! $lock_result )
return;
// Check to see if the lock is still valid
if ( $lock_result > ( time() - HOUR_IN_SECONDS ) )
return;
}
// Update the lock, as by this point we've definitely got a lock, just need to fire the actions
update_option( $lock_name, time() );
if ( ! $this->create_lock( 'auto_updater' ) )
return;
// Don't automatically run these thins, as we'll handle it ourselves
remove_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 );
@ -3092,8 +3154,7 @@ class WP_Automatic_Updater {
do_action( 'automatic_updates_complete', $this->update_results );
}
// Clear the lock
delete_option( $lock_name );
$this->release_lock( 'auto_updater' );
}
/**
@ -3163,7 +3224,7 @@ class WP_Automatic_Updater {
* the issue could actually be on WordPress.org's side.) If that one fails, then email.
*/
$send = true;
$transient_failures = array( 'incompatible_archive', 'download_failed', 'insane_distro' );
$transient_failures = array( 'incompatible_archive', 'download_failed', 'insane_distro', 'locked' );
if ( in_array( $error_code, $transient_failures ) && ! get_site_option( 'auto_core_update_failed' ) ) {
wp_schedule_single_event( time() + HOUR_IN_SECONDS, 'wp_maybe_auto_update' );
$send = false;

View File

@ -484,7 +484,7 @@ function do_core_upgrade( $reinstall = false ) {
if ( is_wp_error($result) ) {
show_message($result);
if ('up_to_date' != $result->get_error_code() )
if ( 'up_to_date' != $result->get_error_code() && 'locked' != $result->get_error_code() )
show_message( __('Installation Failed') );
echo '</div>';
return;

View File

@ -4,7 +4,7 @@
*
* @global string $wp_version
*/
$wp_version = '4.5-alpha-1453176612026';
$wp_version = '4.5-alpha-1453180032918';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.