Cron: Ensure identical single events aren't scheduled less than 10min apart.

Improves the logic in `wp_schedule_single_event()` to ensure an identical event is not scheduled within ten minutes.

This moves the logic for checking for identical events to be self contained rather than relying on `wp_next_scheduled()` as this fails to account for events with a past timestamp when wp-cron fails to trigger or for multiple identical events being scheduled already.

Props bodohugobarwich.
Fixes #44818.



git-svn-id: https://develop.svn.wordpress.org/trunk@44917 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Peter Wilson 2019-03-16 06:41:23 +00:00
parent 7be4aabff6
commit 13bb9c5ef4
2 changed files with 127 additions and 6 deletions

View File

@ -79,9 +79,49 @@ function wp_schedule_single_event( $timestamp, $hook, $args = array() ) {
return $pre;
}
// Don't schedule a duplicate if there's already an identical event due within 10 minutes of it
$next = wp_next_scheduled( $hook, $args );
if ( $next && abs( $next - $timestamp ) <= 10 * MINUTE_IN_SECONDS ) {
/*
* Check for a duplicated event.
*
* Don't schedule an event if there's already an identical event
* within 10 minutes.
*
* When scheduling events within ten minutes of the current time,
* all past identical events are considered duplicates.
*
* When scheduling an event with a past timestamp (ie, before the
* current time) all events scheduled within the next ten minutes
* are considered duplicates.
*/
$crons = (array) _get_cron_array();
$key = md5( serialize( $event->args ) );
$duplicate = false;
if ( $event->timestamp < time() + 10 * MINUTE_IN_SECONDS ) {
$min_timestamp = 0;
} else {
$min_timestamp = $event->timestamp - 10 * MINUTE_IN_SECONDS;
}
if ( $event->timestamp < time() ) {
$max_timestamp = time() + 10 * MINUTE_IN_SECONDS;
} else {
$max_timestamp = $event->timestamp + 10 * MINUTE_IN_SECONDS;
}
foreach ( $crons as $event_timestamp => $cron ) {
if ( $event_timestamp < $min_timestamp ) {
continue;
}
if ( $event_timestamp > $max_timestamp ) {
break;
}
if ( isset( $cron[ $event->hook ][ $key ] ) ) {
$duplicate = true;
break;
}
}
if ( $duplicate ) {
return false;
}
@ -107,9 +147,6 @@ function wp_schedule_single_event( $timestamp, $hook, $args = array() ) {
return false;
}
$key = md5( serialize( $event->args ) );
$crons = _get_cron_array();
$crons[ $event->timestamp ][ $event->hook ][ $key ] = array(
'schedule' => $event->schedule,
'args' => $event->args,

View File

@ -596,4 +596,88 @@ class Tests_Cron extends WP_UnitTestCase {
$this->assertFalse( wp_get_scheduled_event( $hook, $args, 'Words Fail!' ) );
}
/**
* Ensure any past event counts as a duplicate.
*
* @ticket 44818
*/
function test_duplicate_past_event() {
$hook = __FUNCTION__;
$args = array( 'arg1' );
$ts1 = strtotime( '-14 minutes' );
$ts2 = strtotime( '+5 minutes' );
$ts3 = strtotime( '-2 minutes' );
// First event scheduled successfully.
$this->assertTrue( wp_schedule_single_event( $ts1, $hook, $args ) );
// Second event fails.
$this->assertFalse( wp_schedule_single_event( $ts2, $hook, $args ) );
// Third event fails.
$this->assertFalse( wp_schedule_single_event( $ts3, $hook, $args ) );
}
/**
* Ensure any near future event counts as a duplicate.
*
* @ticket 44818
*/
function test_duplicate_near_future_event() {
$hook = __FUNCTION__;
$args = array( 'arg1' );
$ts1 = strtotime( '+4 minutes' );
$ts2 = strtotime( '-15 minutes' );
$ts3 = strtotime( '+12 minutes' );
// First event scheduled successfully.
$this->assertTrue( wp_schedule_single_event( $ts1, $hook, $args ) );
// Second event fails.
$this->assertFalse( wp_schedule_single_event( $ts2, $hook, $args ) );
// Third event fails.
$this->assertFalse( wp_schedule_single_event( $ts3, $hook, $args ) );
}
/**
* Duplicate future events are disallowed.
*
* @ticket 44818
*/
function test_duplicate_future_event() {
$hook = __FUNCTION__;
$args = array( 'arg1' );
$ts1 = strtotime( '+15 minutes' );
$ts2 = strtotime( '-600 seconds', $ts1 );
$ts3 = strtotime( '+600 seconds', $ts1 );
// First event scheduled successfully.
$this->assertTrue( wp_schedule_single_event( $ts1, $hook, $args ) );
// Events within ten minutes should fail.
$this->assertFalse( wp_schedule_single_event( $ts2, $hook, $args ) );
$this->assertFalse( wp_schedule_single_event( $ts3, $hook, $args ) );
}
/**
* Future events are allowed.
*
* @ticket 44818
*/
function test_not_duplicate_future_event() {
$hook = __FUNCTION__;
$args = array( 'arg1' );
$ts1 = strtotime( '+15 minutes' );
$ts2 = strtotime( '-601 seconds', $ts1 );
$ts3 = strtotime( '+601 seconds', $ts1 );
// First event scheduled successfully.
$this->assertTrue( wp_schedule_single_event( $ts1, $hook, $args ) );
// Events over ten minutes should work.
$this->assertTrue( wp_schedule_single_event( $ts2, $hook, $args ) );
$this->assertTrue( wp_schedule_single_event( $ts3, $hook, $args ) );
}
}