Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 85 additions & 24 deletions classes/wp-background-process.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ abstract class WP_Background_Process extends WP_Async_Request {
*/
private $chain_id;

/**
* Whether the background process is switched to process
* a batch from another blog in the multisite
*
* @var bool
*/
private $switched_to_blog = false;

/**
* Action
*
Expand Down Expand Up @@ -67,6 +75,13 @@ abstract class WP_Background_Process extends WP_Async_Request {
*/
protected $allowed_batch_data_classes = true;

/**
* Amount of seconds to sleep() between batches
*
* @var int
*/
protected $seconds_between_batches = 0;

/**
* The status set when process is cancelling.
*
Expand Down Expand Up @@ -319,7 +334,14 @@ public function is_active() {
*/
protected function generate_key( $length = 64, $key = 'batch' ) {
$unique = md5( microtime() . wp_rand() );
$prepend = $this->identifier . '_' . $key . '_';
$prepend = $this->identifier . '_' . $key;

if( is_multisite() ){
$site_id = get_current_blog_id();
$prepend .= '_' . $site_id;
}

$prepend .= '_';

return substr( $prepend . $unique, 0, $length );
}
Expand Down Expand Up @@ -506,29 +528,30 @@ protected function unlock_process() {
return $this;
}

/**
* Get batch.
*
* @return stdClass Return the first batch of queued items.
*/
protected function get_batch() {
/**
* Get batch.
*
* @param int $for_site_id Get batch for a specific Site ID (multisite)
* @return stdClass Return the first batch of queued items.
*/
protected function get_batch($for_site_id = null) {
return array_reduce(
$this->get_batches( 1 ),
$this->get_batches( 1, $for_site_id ),
static function ( $carry, $batch ) {
return $batch;
},
array()
);
}

/**
* Get batches.
*
* @param int $limit Number of batches to return, defaults to all.
*
* @return array of stdClass
*/
public function get_batches( $limit = 0 ) {
/**
* Get batches.
*
* @param int $limit Number of batches to return, defaults to all.
* @param int $for_site_id Get batches for a specific Site ID (multisite)
* @return array of stdClass
*/
public function get_batches( $limit = 0, $for_site_id = null ) {
global $wpdb;

if ( empty( $limit ) || ! is_int( $limit ) ) {
Expand All @@ -547,7 +570,12 @@ public function get_batches( $limit = 0 ) {
$value_column = 'meta_value';
}

$key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
if( !is_null( $for_site_id ) ) {
$key = $wpdb->esc_like( $this->identifier . '_batch_' . $for_site_id ) . '%';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you probably need '_%' rather than '%' at the end here, otherwise you may be selecting for all subsites whose ID begins with the required ID, and any batch without a subsite ID that happens to have a generated key that begins with the subsite ID.

e.g. for subsite ID = 1, the following are going to match:

wibble_batch_1_1abcde <-- GOOD
wibble_batch_10_1abcde <-- BAD
wibble_batch_11_1abcde <-- BAD
wibble_batch_101_1abcde <-- BAD
wibble_batch_1abcde <-- BAD

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I think you might be right about that.

}else{
$key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
}


$sql = '
SELECT *
Expand Down Expand Up @@ -578,9 +606,12 @@ public function get_batches( $limit = 0 ) {

$batches = array_map(
static function ( $item ) use ( $column, $value_column, $allowed_classes ) {
$batch = new stdClass();
$batch->key = $item->{$column};
$batch->data = static::maybe_unserialize( $item->{$value_column}, $allowed_classes );
$batch = new stdClass();
$batch->key = $item->{$column};
if( is_multisite() ){
$batch->site_id = static::extract_site_id_from_column_name($item->{$column});
}
$batch->data = static::maybe_unserialize( $item->{$value_column}, $allowed_classes );

return $batch;
},
Expand All @@ -591,6 +622,20 @@ static function ( $item ) use ( $column, $value_column, $allowed_classes ) {
return $batches;
}

/**
* Extract the site ID from the database column name when in a multisite environment
*
* @param $column_name
* @return int|null
*/
protected static function extract_site_id_from_column_name($column_name ) {
if ( preg_match( '/_batch_(\d+)_/', $column_name, $matches ) ) {
return intval( $matches[1] );
}

return null;
}

/**
* Handle a dispatched request.
*
Expand All @@ -606,7 +651,7 @@ protected function handle() {
* @param int $seconds
*/
$throttle_seconds = max(
0,
$this->seconds_between_batches,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you probably meant for this change to apply to the value passed into the inner apply_filters?

apply_filters(
$this->identifier . '_seconds_between_batches',
apply_filters(
Expand All @@ -619,6 +664,18 @@ protected function handle() {
do {
$batch = $this->get_batch();

if( is_multisite() && !is_null( $batch->site_id ) && $batch->site_id !== get_current_blog_id() ) {

//Do not try to run a batch for a site that doesn't exist (anymore)
if ( ! get_site( $batch->site_id ) ) {
$this->delete( $batch->key );
continue;
}

switch_to_blog( $batch->site_id );
$this->switched_to_blog = true;
}

foreach ( $batch->data as $key => $value ) {
$task = $this->task( $value );

Expand All @@ -633,9 +690,6 @@ protected function handle() {
$this->update( $batch->key, $batch->data );
}

// Let the server breathe a little.
sleep( $throttle_seconds );

// Batch limits reached, or pause or cancel requested.
if ( ! $this->should_continue() ) {
break;
Expand All @@ -646,6 +700,13 @@ protected function handle() {
if ( empty( $batch->data ) ) {
$this->delete( $batch->key );
}

if( $this->switched_to_blog ) {
restore_current_blog();
}

// Let the server breathe a little.
sleep( $throttle_seconds );
} while ( ! $this->is_queue_empty() && $this->should_continue() );

$this->unlock_process();
Expand Down