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
*
* Uses https://github.com/A5hleyRich/wp-background-processing to handle emails
* in the background.
*
* @class WC_Background_Emailer
* @version 3.0.1
* @package WooCommerce/Classes
* @category Class
* @author WooCommerce
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( 'WP_Async_Request', false ) ) {
include_once( dirname( __FILE__ ) . '/libraries/wp-async-request.php' );
}
if ( ! class_exists( 'WP_Background_Process', false ) ) {
include_once( dirname( __FILE__ ) . '/libraries/wp-background-process.php' );
if ( ! class_exists( 'WC_Background_Process', false ) ) {
include_once dirname( __FILE__ ) . '/abstracts/class-wc-background-process.php';
}
/**
* WC_Background_Emailer Class.
*/
class WC_Background_Emailer extends WP_Background_Process {
class WC_Background_Emailer extends WC_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
* item from the queue.
*
* @param array $callback Update callback function
* @param array $callback Update callback function.
* @return mixed
*/
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'] );
} catch ( Exception $e ) {
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 ) {
continue;
}
$cookies[] = new WP_Http_Cookie( array( 'name' => $name, 'value' => $value ) );
$cookies[] = new WP_Http_Cookie( array(
'name' => $name,
'value' => $value,
) );
}
return array(

View File

@ -2,31 +2,22 @@
/**
* 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
* @package WooCommerce/Classes
* @category Class
* @author WooThemes
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( 'WP_Async_Request', false ) ) {
include_once( dirname( __FILE__ ) . '/libraries/wp-async-request.php' );
}
if ( ! class_exists( 'WP_Background_Process', false ) ) {
include_once( dirname( __FILE__ ) . '/libraries/wp-background-process.php' );
if ( ! class_exists( 'WC_Background_Process', false ) ) {
include_once dirname( __FILE__ ) . '/abstracts/class-wc-background-process.php';
}
/**
* WC_Background_Updater Class.
*/
class WC_Background_Updater extends WP_Background_Process {
class WC_Background_Updater extends WC_Background_Process {
/**
* Initiate new background process.
@ -88,6 +79,7 @@ class WC_Background_Updater extends WP_Background_Process {
/**
* Is the updater running?
*
* @return boolean
*/
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
* item from the queue.
*
* @param string $callback Update callback function
* @param string $callback Update callback function.
* @return mixed
*/
protected function task( $callback ) {
@ -110,7 +102,7 @@ class WC_Background_Updater extends WP_Background_Process {
$logger = wc_get_logger();
include_once( dirname( __FILE__ ) . '/wc-update-functions.php' );
include_once dirname( __FILE__ ) . '/wc-update-functions.php';
if ( is_callable( $callback ) ) {
$logger->info( sprintf( 'Running %s callback', $callback ), array( 'source' => 'wc_db_updates' ) );

View File

@ -9,18 +9,14 @@
defined( 'ABSPATH' ) || exit;
if ( ! class_exists( 'WP_Async_Request', false ) ) {
include_once dirname( __FILE__ ) . '/libraries/wp-async-request.php';
}
if ( ! class_exists( 'WP_Background_Process', false ) ) {
include_once dirname( __FILE__ ) . '/libraries/wp-background-process.php';
if ( ! class_exists( 'WC_Background_Process', false ) ) {
include_once dirname( __FILE__ ) . '/abstracts/class-wc-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.

View File

@ -187,7 +187,7 @@ class WC_Regenerate_Images {
private static function queue_image_regeneration() {
global $wpdb;
// 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.
$images = $wpdb->get_results( // @codingStandardsIgnoreLine

View File

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

View File

@ -9,7 +9,7 @@
defined( 'ABSPATH' ) || exit;
/**
* WP_Background_Process class.
* Abstract WP_Background_Process class.
*/
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';
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() {
// Schedule the cron healthcheck.
@ -120,27 +123,12 @@ abstract class WP_Background_Process extends WP_Async_Request {
/**
* Delete queue
*
* @param string $key Key. Leave blank to delete all batches.
* @param string $key Key.
*
* @return $this
*/
public function delete( $key = '' ) {
if ( $key ) {
public function delete( $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;
}
@ -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
* the process is not already running.
*/
public function maybe_handle() {
// Don't lock up other requests while processing.
session_write_close(); // @codingStandardsIgnoreLine.
// Don't lock up other requests while processing
session_write_close();
if ( $this->is_process_running() ) {
// Background process already running.
@ -205,9 +193,13 @@ abstract class WP_Background_Process extends WP_Async_Request {
$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;
}
@ -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.
* 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';
}
$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->key = $query->$column;
@ -287,15 +285,6 @@ abstract class WP_Background_Process extends WP_Async_Request {
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
*
@ -317,7 +306,7 @@ abstract class WP_Background_Process extends WP_Async_Request {
unset( $batch->data[ $key ] );
}
if ( $this->batch_limit_exceeded() ) {
if ( $this->time_exceeded() || $this->memory_exceeded() ) {
// Batch limits reached.
break;
}
@ -329,7 +318,7 @@ abstract class WP_Background_Process extends WP_Async_Request {
} else {
$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();
@ -339,6 +328,8 @@ abstract class WP_Background_Process extends WP_Async_Request {
} else {
$this->complete();
}
wp_die();
}
/**
@ -374,7 +365,7 @@ abstract class WP_Background_Process extends WP_Async_Request {
$memory_limit = '128M';
}
if ( ! $memory_limit || -1 === intval( $memory_limit ) ) {
if ( ! $memory_limit || -1 === $memory_limit ) {
// Unlimited, set to 32GB.
$memory_limit = '32000M';
}
@ -429,8 +420,7 @@ abstract class WP_Background_Process extends WP_Async_Request {
// 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 ),
'display' => sprintf( __( 'Every %d Minutes' ), $interval ),
);
return $schedules;
@ -483,12 +473,17 @@ abstract class WP_Background_Process extends WP_Async_Request {
* Cancel Process
*
* Stop processing queue items, clear cronjob and delete batch.
*
*/
public function cancel_process() {
if ( ! $this->is_queue_empty() ) {
$this->delete();
$batch = $this->get_batch();
$this->delete( $batch->key );
wp_clear_scheduled_hook( $this->cron_hook_identifier );
}
}
/**
@ -504,4 +499,5 @@ abstract class WP_Background_Process extends WP_Async_Request {
* @return mixed
*/
abstract protected function task( $item );
}

View File

@ -1,12 +1,12 @@
<?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
*/
class WC_Mock_Background_Process extends WP_Background_Process {
class WC_Mock_Background_Process extends WC_Background_Process {
/**
* Constructor.
*/