Merge pull request #18752 from woocommerce/refactor/background-processing

Introduced new WC_Background_Process abstract class
This commit is contained in:
Mike Jolley 2018-02-01 16:18:56 +00:00 committed by GitHub
commit db54911443
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 286 additions and 94 deletions

View File

@ -0,0 +1,212 @@
<?php
/**
* Abstract WP_Background_Process class.
*
* Uses https://github.com/A5hleyRich/wp-background-processing to handle DB
* updates in the background.
*
* @package WooCommerce/Classes
*/
defined( 'ABSPATH' ) || exit;
if ( ! class_exists( 'WP_Async_Request', false ) ) {
include_once dirname( WC_PLUGIN_FILE ) . '/includes/libraries/wp-async-request.php';
}
if ( ! class_exists( 'WP_Background_Process', false ) ) {
include_once dirname( WC_PLUGIN_FILE ) . '/includes/libraries/wp-background-process.php';
}
/**
* WC_Background_Process class.
*/
abstract class WC_Background_Process extends WP_Background_Process {
/**
* Is queue empty.
*
* @return bool
*/
protected function is_queue_empty() {
global $wpdb;
$table = $wpdb->options;
$column = 'option_name';
if ( is_multisite() ) {
$table = $wpdb->sitemeta;
$column = 'meta_key';
}
$key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
$count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE {$column} LIKE %s", $key ) ); // @codingStandardsIgnoreLine.
return ( $count > 0 ) ? false : true;
}
/**
* Get batch.
*
* @return stdClass Return the first batch from the queue.
*/
protected function get_batch() {
global $wpdb;
$table = $wpdb->options;
$column = 'option_name';
$key_column = 'option_id';
$value_column = 'option_value';
if ( is_multisite() ) {
$table = $wpdb->sitemeta;
$column = 'meta_key';
$key_column = 'meta_id';
$value_column = 'meta_value';
}
$key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
$query = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE {$column} LIKE %s ORDER BY {$key_column} ASC LIMIT 1", $key ) ); // @codingStandardsIgnoreLine.
$batch = new stdClass();
$batch->key = $query->$column;
$batch->data = maybe_unserialize( $query->$value_column );
return $batch;
}
/**
* See if the batch limit has been exceeded.
*
* @return bool
*/
protected function batch_limit_exceeded() {
return $this->time_exceeded() || $this->memory_exceeded();
}
/**
* Handle.
*
* Pass each queue item to the task handler, while remaining
* within server memory and time limit constraints.
*/
protected function handle() {
$this->lock_process();
do {
$batch = $this->get_batch();
foreach ( $batch->data as $key => $value ) {
$task = $this->task( $value );
if ( false !== $task ) {
$batch->data[ $key ] = $task;
} else {
unset( $batch->data[ $key ] );
}
if ( $this->batch_limit_exceeded() ) {
// Batch limits reached.
break;
}
}
// Update or delete current batch.
if ( ! empty( $batch->data ) ) {
$this->update( $batch->key, $batch->data );
} else {
$this->delete( $batch->key );
}
} while ( ! $this->batch_limit_exceeded() && ! $this->is_queue_empty() );
$this->unlock_process();
// Start next batch or complete process.
if ( ! $this->is_queue_empty() ) {
$this->dispatch();
} else {
$this->complete();
}
}
/**
* Get memory limit.
*
* @return int
*/
protected function get_memory_limit() {
if ( function_exists( 'ini_get' ) ) {
$memory_limit = ini_get( 'memory_limit' );
} else {
// Sensible default.
$memory_limit = '128M';
}
if ( ! $memory_limit || -1 === intval( $memory_limit ) ) {
// Unlimited, set to 32GB.
$memory_limit = '32000M';
}
return intval( $memory_limit ) * 1024 * 1024;
}
/**
* Schedule cron healthcheck.
*
* @param array $schedules Schedules.
* @return array
*/
public function schedule_cron_healthcheck( $schedules ) {
$interval = apply_filters( $this->identifier . '_cron_interval', 5 );
if ( property_exists( $this, 'cron_interval' ) ) {
$interval = apply_filters( $this->identifier . '_cron_interval', $this->cron_interval_identifier );
}
// Adds every 5 minutes to the existing schedules.
$schedules[ $this->identifier . '_cron_interval' ] = array(
'interval' => MINUTE_IN_SECONDS * $interval,
/* translators: %d: interval */
'display' => sprintf( __( 'Every %d minutes', 'woocommerce' ), $interval ),
);
return $schedules;
}
/**
* Delete all batches.
*
* @return WC_Background_Process
*/
public function delete_all_batches() {
global $wpdb;
$table = $wpdb->options;
$column = 'option_name';
if ( is_multisite() ) {
$table = $wpdb->sitemeta;
$column = 'meta_key';
}
$key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
$wpdb->query( $wpdb->prepare( "DELETE FROM {$table} WHERE {$column} LIKE %s", $key ) ); // @codingStandardsIgnoreLine.
return $this;
}
/**
* Kill process.
*
* Stop processing queue items, clear cronjob and delete all batches.
*/
public function kill_process() {
if ( ! $this->is_queue_empty() ) {
$this->delete_all_batches();
wp_clear_scheduled_hook( $this->cron_hook_identifier );
}
}
}

View File

@ -2,31 +2,22 @@
/** /**
* Background Emailer * Background Emailer
* *
* Uses https://github.com/A5hleyRich/wp-background-processing to handle emails
* in the background.
*
* @class WC_Background_Emailer
* @version 3.0.1 * @version 3.0.1
* @package WooCommerce/Classes * @package WooCommerce/Classes
* @category Class
* @author WooCommerce
*/ */
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
if ( ! class_exists( 'WP_Async_Request', false ) ) { if ( ! class_exists( 'WC_Background_Process', false ) ) {
include_once( dirname( __FILE__ ) . '/libraries/wp-async-request.php' ); include_once dirname( __FILE__ ) . '/abstracts/class-wc-background-process.php';
}
if ( ! class_exists( 'WP_Background_Process', false ) ) {
include_once( dirname( __FILE__ ) . '/libraries/wp-background-process.php' );
} }
/** /**
* WC_Background_Emailer Class. * WC_Background_Emailer Class.
*/ */
class WC_Background_Emailer extends WP_Background_Process { class WC_Background_Emailer extends WC_Background_Process {
/** /**
* Initiate new background process. * Initiate new background process.
@ -59,7 +50,7 @@ class WC_Background_Emailer extends WP_Background_Process {
* in the next pass through. Or, return false to remove the * in the next pass through. Or, return false to remove the
* item from the queue. * item from the queue.
* *
* @param array $callback Update callback function * @param array $callback Update callback function.
* @return mixed * @return mixed
*/ */
protected function task( $callback ) { protected function task( $callback ) {
@ -68,7 +59,7 @@ class WC_Background_Emailer extends WP_Background_Process {
WC_Emails::send_queued_transactional_email( $callback['filter'], $callback['args'] ); WC_Emails::send_queued_transactional_email( $callback['filter'], $callback['args'] );
} catch ( Exception $e ) { } catch ( Exception $e ) {
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
trigger_error( 'Transactional email triggered fatal error for callback ' . $callback['filter'], E_USER_WARNING ); trigger_error( 'Transactional email triggered fatal error for callback ' . esc_html( $callback['filter'] ), E_USER_WARNING );
} }
} }
} }
@ -130,7 +121,10 @@ class WC_Background_Emailer extends WP_Background_Process {
if ( 'PHPSESSID' === $name ) { if ( 'PHPSESSID' === $name ) {
continue; continue;
} }
$cookies[] = new WP_Http_Cookie( array( 'name' => $name, 'value' => $value ) ); $cookies[] = new WP_Http_Cookie( array(
'name' => $name,
'value' => $value,
) );
} }
return array( return array(

View File

@ -2,31 +2,22 @@
/** /**
* Background Updater * Background Updater
* *
* Uses https://github.com/A5hleyRich/wp-background-processing to handle DB
* updates in the background.
*
* @class WC_Background_Updater
* @version 2.6.0 * @version 2.6.0
* @package WooCommerce/Classes * @package WooCommerce/Classes
* @category Class
* @author WooThemes
*/ */
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
if ( ! class_exists( 'WP_Async_Request', false ) ) { if ( ! class_exists( 'WC_Background_Process', false ) ) {
include_once( dirname( __FILE__ ) . '/libraries/wp-async-request.php' ); include_once dirname( __FILE__ ) . '/abstracts/class-wc-background-process.php';
}
if ( ! class_exists( 'WP_Background_Process', false ) ) {
include_once( dirname( __FILE__ ) . '/libraries/wp-background-process.php' );
} }
/** /**
* WC_Background_Updater Class. * WC_Background_Updater Class.
*/ */
class WC_Background_Updater extends WP_Background_Process { class WC_Background_Updater extends WC_Background_Process {
/** /**
* Initiate new background process. * Initiate new background process.
@ -88,6 +79,7 @@ class WC_Background_Updater extends WP_Background_Process {
/** /**
* Is the updater running? * Is the updater running?
*
* @return boolean * @return boolean
*/ */
public function is_updating() { public function is_updating() {
@ -102,7 +94,7 @@ class WC_Background_Updater extends WP_Background_Process {
* in the next pass through. Or, return false to remove the * in the next pass through. Or, return false to remove the
* item from the queue. * item from the queue.
* *
* @param string $callback Update callback function * @param string $callback Update callback function.
* @return mixed * @return mixed
*/ */
protected function task( $callback ) { protected function task( $callback ) {
@ -110,7 +102,7 @@ class WC_Background_Updater extends WP_Background_Process {
$logger = wc_get_logger(); $logger = wc_get_logger();
include_once( dirname( __FILE__ ) . '/wc-update-functions.php' ); include_once dirname( __FILE__ ) . '/wc-update-functions.php';
if ( is_callable( $callback ) ) { if ( is_callable( $callback ) ) {
$logger->info( sprintf( 'Running %s callback', $callback ), array( 'source' => 'wc_db_updates' ) ); $logger->info( sprintf( 'Running %s callback', $callback ), array( 'source' => 'wc_db_updates' ) );

View File

@ -9,18 +9,14 @@
defined( 'ABSPATH' ) || exit; defined( 'ABSPATH' ) || exit;
if ( ! class_exists( 'WP_Async_Request', false ) ) { if ( ! class_exists( 'WC_Background_Process', false ) ) {
include_once dirname( __FILE__ ) . '/libraries/wp-async-request.php'; include_once dirname( __FILE__ ) . '/abstracts/class-wc-background-process.php';
}
if ( ! class_exists( 'WP_Background_Process', false ) ) {
include_once dirname( __FILE__ ) . '/libraries/wp-background-process.php';
} }
/** /**
* Class that extends WP_Background_Process to process image regeneration in the background * Class that extends WC_Background_Process to process image regeneration in the background.
*/ */
class WC_Regenerate_Images_Request extends WP_Background_Process { class WC_Regenerate_Images_Request extends WC_Background_Process {
/** /**
* Stores the attachment ID being processed. * Stores the attachment ID being processed.

View File

@ -187,7 +187,7 @@ class WC_Regenerate_Images {
private static function queue_image_regeneration() { private static function queue_image_regeneration() {
global $wpdb; global $wpdb;
// First lets cancel existing running queue to avoid running it more than once. // First lets cancel existing running queue to avoid running it more than once.
self::$background_process->cancel_process(); self::$background_process->kill_process();
// Now lets find all product image attachments IDs and pop them onto the queue. // Now lets find all product image attachments IDs and pop them onto the queue.
$images = $wpdb->get_results( // @codingStandardsIgnoreLine $images = $wpdb->get_results( // @codingStandardsIgnoreLine

View File

@ -1,13 +1,14 @@
<?php <?php
if ( ! defined( 'ABSPATH' ) ) { /**
exit; * WP Async Request
} *
* @package WP-Background-Processing
*/
defined( 'ABSPATH' ) || exit;
/** /**
* Abstract WP_Async_Request class. * Abstract WP_Async_Request class.
*
* @package WP-Background-Processing
* @abstract
*/ */
abstract class WP_Async_Request { abstract class WP_Async_Request {
@ -155,4 +156,5 @@ abstract class WP_Async_Request {
* during the async request. * during the async request.
*/ */
abstract protected function handle(); abstract protected function handle();
} }

View File

@ -9,7 +9,7 @@
defined( 'ABSPATH' ) || exit; defined( 'ABSPATH' ) || exit;
/** /**
* WP_Background_Process class. * Abstract WP_Background_Process class.
*/ */
abstract class WP_Background_Process extends WP_Async_Request { abstract class WP_Background_Process extends WP_Async_Request {
@ -59,11 +59,14 @@ abstract class WP_Background_Process extends WP_Async_Request {
$this->cron_interval_identifier = $this->identifier . '_cron_interval'; $this->cron_interval_identifier = $this->identifier . '_cron_interval';
add_action( $this->cron_hook_identifier, array( $this, 'handle_cron_healthcheck' ) ); add_action( $this->cron_hook_identifier, array( $this, 'handle_cron_healthcheck' ) );
add_filter( 'cron_schedules', array( $this, 'schedule_cron_healthcheck' ) ); // @codingStandardsIgnoreLine. add_filter( 'cron_schedules', array( $this, 'schedule_cron_healthcheck' ) );
} }
/** /**
* Dispatch. * Dispatch
*
* @access public
* @return void
*/ */
public function dispatch() { public function dispatch() {
// Schedule the cron healthcheck. // Schedule the cron healthcheck.
@ -120,27 +123,12 @@ abstract class WP_Background_Process extends WP_Async_Request {
/** /**
* Delete queue * Delete queue
* *
* @param string $key Key. Leave blank to delete all batches. * @param string $key Key.
*
* @return $this * @return $this
*/ */
public function delete( $key = '' ) { public function delete( $key ) {
if ( $key ) { delete_site_option( $key );
delete_site_option( $key );
} else {
global $wpdb;
$table = $wpdb->options;
$column = 'option_name';
if ( is_multisite() ) {
$table = $wpdb->sitemeta;
$column = 'meta_key';
}
$key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
$wpdb->query( $wpdb->prepare( "DELETE FROM {$table} WHERE {$column} LIKE %s", $key ) ); // @codingStandardsIgnoreLine.
}
return $this; return $this;
} }
@ -163,14 +151,14 @@ abstract class WP_Background_Process extends WP_Async_Request {
} }
/** /**
* Maybe process queue. * Maybe process queue
* *
* Checks whether data exists within the queue and that * Checks whether data exists within the queue and that
* the process is not already running. * the process is not already running.
*/ */
public function maybe_handle() { public function maybe_handle() {
// Don't lock up other requests while processing. // Don't lock up other requests while processing
session_write_close(); // @codingStandardsIgnoreLine. session_write_close();
if ( $this->is_process_running() ) { if ( $this->is_process_running() ) {
// Background process already running. // Background process already running.
@ -205,9 +193,13 @@ abstract class WP_Background_Process extends WP_Async_Request {
$column = 'meta_key'; $column = 'meta_key';
} }
$key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%'; $key = $this->identifier . '_batch_%';
$count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE {$column} LIKE %s", $key ) ); // @codingStandardsIgnoreLine. $count = $wpdb->get_var( $wpdb->prepare( "
SELECT COUNT(*)
FROM {$table}
WHERE {$column} LIKE %s
", $key ) );
return ( $count > 0 ) ? false : true; return ( $count > 0 ) ? false : true;
} }
@ -228,7 +220,7 @@ abstract class WP_Background_Process extends WP_Async_Request {
} }
/** /**
* Lock process. * Lock process
* *
* Lock the process so that multiple instances can't run simultaneously. * Lock the process so that multiple instances can't run simultaneously.
* Override if applicable, but the duration should be greater than that * Override if applicable, but the duration should be greater than that
@ -276,9 +268,15 @@ abstract class WP_Background_Process extends WP_Async_Request {
$value_column = 'meta_value'; $value_column = 'meta_value';
} }
$key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%'; $key = $this->identifier . '_batch_%';
$query = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE {$column} LIKE %s ORDER BY {$key_column} ASC LIMIT 1", $key ) ); // @codingStandardsIgnoreLine. $query = $wpdb->get_row( $wpdb->prepare( "
SELECT *
FROM {$table}
WHERE {$column} LIKE %s
ORDER BY {$key_column} ASC
LIMIT 1
", $key ) );
$batch = new stdClass(); $batch = new stdClass();
$batch->key = $query->$column; $batch->key = $query->$column;
@ -287,15 +285,6 @@ abstract class WP_Background_Process extends WP_Async_Request {
return $batch; return $batch;
} }
/**
* See if the batch limit has been exceeded.
*
* @return bool
*/
protected function batch_limit_exceeded() {
return $this->time_exceeded() || $this->memory_exceeded();
}
/** /**
* Handle * Handle
* *
@ -317,7 +306,7 @@ abstract class WP_Background_Process extends WP_Async_Request {
unset( $batch->data[ $key ] ); unset( $batch->data[ $key ] );
} }
if ( $this->batch_limit_exceeded() ) { if ( $this->time_exceeded() || $this->memory_exceeded() ) {
// Batch limits reached. // Batch limits reached.
break; break;
} }
@ -329,7 +318,7 @@ abstract class WP_Background_Process extends WP_Async_Request {
} else { } else {
$this->delete( $batch->key ); $this->delete( $batch->key );
} }
} while ( ! $this->batch_limit_exceeded() && ! $this->is_queue_empty() ); } while ( ! $this->time_exceeded() && ! $this->memory_exceeded() && ! $this->is_queue_empty() );
$this->unlock_process(); $this->unlock_process();
@ -339,6 +328,8 @@ abstract class WP_Background_Process extends WP_Async_Request {
} else { } else {
$this->complete(); $this->complete();
} }
wp_die();
} }
/** /**
@ -374,7 +365,7 @@ abstract class WP_Background_Process extends WP_Async_Request {
$memory_limit = '128M'; $memory_limit = '128M';
} }
if ( ! $memory_limit || -1 === intval( $memory_limit ) ) { if ( ! $memory_limit || -1 === $memory_limit ) {
// Unlimited, set to 32GB. // Unlimited, set to 32GB.
$memory_limit = '32000M'; $memory_limit = '32000M';
} }
@ -429,8 +420,7 @@ abstract class WP_Background_Process extends WP_Async_Request {
// Adds every 5 minutes to the existing schedules. // Adds every 5 minutes to the existing schedules.
$schedules[ $this->identifier . '_cron_interval' ] = array( $schedules[ $this->identifier . '_cron_interval' ] = array(
'interval' => MINUTE_IN_SECONDS * $interval, 'interval' => MINUTE_IN_SECONDS * $interval,
/* translators: %d: interval */ 'display' => sprintf( __( 'Every %d Minutes' ), $interval ),
'display' => sprintf( __( 'Every %d minutes', 'woocommerce' ), $interval ),
); );
return $schedules; return $schedules;
@ -483,12 +473,17 @@ abstract class WP_Background_Process extends WP_Async_Request {
* Cancel Process * Cancel Process
* *
* Stop processing queue items, clear cronjob and delete batch. * Stop processing queue items, clear cronjob and delete batch.
*
*/ */
public function cancel_process() { public function cancel_process() {
if ( ! $this->is_queue_empty() ) { if ( ! $this->is_queue_empty() ) {
$this->delete(); $batch = $this->get_batch();
$this->delete( $batch->key );
wp_clear_scheduled_hook( $this->cron_hook_identifier ); wp_clear_scheduled_hook( $this->cron_hook_identifier );
} }
} }
/** /**
@ -504,4 +499,5 @@ abstract class WP_Background_Process extends WP_Async_Request {
* @return mixed * @return mixed
*/ */
abstract protected function task( $item ); abstract protected function task( $item );
} }

View File

@ -1,12 +1,12 @@
<?php <?php
/** /**
* Test WP_Background_Process functionality * Test WC_Background_Process functionality
* *
* A mock class for testing the WP_Background_Process functionality. * A mock class for testing the WC_Background_Process functionality.
* *
* @since 3.3 * @since 3.3
*/ */
class WC_Mock_Background_Process extends WP_Background_Process { class WC_Mock_Background_Process extends WC_Background_Process {
/** /**
* Constructor. * Constructor.
*/ */