Make WordPress Core

Changeset 44917


Ignore:
Timestamp:
03/16/2019 06:41:23 AM (5 years ago)
Author:
peterwilsoncc
Message:

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.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/cron.php

    r44694 r44917  
    8080    }
    8181
    82     // Don't schedule a duplicate if there's already an identical event due within 10 minutes of it
    83     $next = wp_next_scheduled( $hook, $args );
    84     if ( $next && abs( $next - $timestamp ) <= 10 * MINUTE_IN_SECONDS ) {
     82    /*
     83     * Check for a duplicated event.
     84     *
     85     * Don't schedule an event if there's already an identical event
     86     * within 10 minutes.
     87     *
     88     * When scheduling events within ten minutes of the current time,
     89     * all past identical events are considered duplicates.
     90     *
     91     * When scheduling an event with a past timestamp (ie, before the
     92     * current time) all events scheduled within the next ten minutes
     93     * are considered duplicates.
     94     */
     95    $crons     = (array) _get_cron_array();
     96    $key       = md5( serialize( $event->args ) );
     97    $duplicate = false;
     98
     99    if ( $event->timestamp < time() + 10 * MINUTE_IN_SECONDS ) {
     100        $min_timestamp = 0;
     101    } else {
     102        $min_timestamp = $event->timestamp - 10 * MINUTE_IN_SECONDS;
     103    }
     104
     105    if ( $event->timestamp < time() ) {
     106        $max_timestamp = time() + 10 * MINUTE_IN_SECONDS;
     107    } else {
     108        $max_timestamp = $event->timestamp + 10 * MINUTE_IN_SECONDS;
     109    }
     110
     111    foreach ( $crons as $event_timestamp => $cron ) {
     112        if ( $event_timestamp < $min_timestamp ) {
     113            continue;
     114        }
     115        if ( $event_timestamp > $max_timestamp ) {
     116            break;
     117        }
     118        if ( isset( $cron[ $event->hook ][ $key ] ) ) {
     119            $duplicate = true;
     120            break;
     121        }
     122    }
     123
     124    if ( $duplicate ) {
    85125        return false;
    86126    }
     
    108148    }
    109149
    110     $key = md5( serialize( $event->args ) );
    111 
    112     $crons = _get_cron_array();
    113150    $crons[ $event->timestamp ][ $event->hook ][ $key ] = array(
    114151        'schedule' => $event->schedule,
  • trunk/tests/phpunit/tests/cron.php

    r44693 r44917  
    597597
    598598    }
     599
     600    /**
     601     * Ensure any past event counts as a duplicate.
     602     *
     603     * @ticket 44818
     604     */
     605    function test_duplicate_past_event() {
     606        $hook = __FUNCTION__;
     607        $args = array( 'arg1' );
     608        $ts1  = strtotime( '-14 minutes' );
     609        $ts2  = strtotime( '+5 minutes' );
     610        $ts3  = strtotime( '-2 minutes' );
     611
     612        // First event scheduled successfully.
     613        $this->assertTrue( wp_schedule_single_event( $ts1, $hook, $args ) );
     614
     615        // Second event fails.
     616        $this->assertFalse( wp_schedule_single_event( $ts2, $hook, $args ) );
     617
     618        // Third event fails.
     619        $this->assertFalse( wp_schedule_single_event( $ts3, $hook, $args ) );
     620    }
     621
     622    /**
     623     * Ensure any near future event counts as a duplicate.
     624     *
     625     * @ticket 44818
     626     */
     627    function test_duplicate_near_future_event() {
     628        $hook = __FUNCTION__;
     629        $args = array( 'arg1' );
     630        $ts1  = strtotime( '+4 minutes' );
     631        $ts2  = strtotime( '-15 minutes' );
     632        $ts3  = strtotime( '+12 minutes' );
     633
     634        // First event scheduled successfully.
     635        $this->assertTrue( wp_schedule_single_event( $ts1, $hook, $args ) );
     636
     637        // Second event fails.
     638        $this->assertFalse( wp_schedule_single_event( $ts2, $hook, $args ) );
     639
     640        // Third event fails.
     641        $this->assertFalse( wp_schedule_single_event( $ts3, $hook, $args ) );
     642    }
     643
     644    /**
     645     * Duplicate future events are disallowed.
     646     *
     647     * @ticket 44818
     648     */
     649    function test_duplicate_future_event() {
     650        $hook = __FUNCTION__;
     651        $args = array( 'arg1' );
     652        $ts1  = strtotime( '+15 minutes' );
     653        $ts2  = strtotime( '-600 seconds', $ts1 );
     654        $ts3  = strtotime( '+600 seconds', $ts1 );
     655
     656        // First event scheduled successfully.
     657        $this->assertTrue( wp_schedule_single_event( $ts1, $hook, $args ) );
     658
     659        // Events within ten minutes should fail.
     660        $this->assertFalse( wp_schedule_single_event( $ts2, $hook, $args ) );
     661        $this->assertFalse( wp_schedule_single_event( $ts3, $hook, $args ) );
     662    }
     663
     664    /**
     665     * Future events are allowed.
     666     *
     667     * @ticket 44818
     668     */
     669    function test_not_duplicate_future_event() {
     670        $hook = __FUNCTION__;
     671        $args = array( 'arg1' );
     672        $ts1  = strtotime( '+15 minutes' );
     673        $ts2  = strtotime( '-601 seconds', $ts1 );
     674        $ts3  = strtotime( '+601 seconds', $ts1 );
     675
     676        // First event scheduled successfully.
     677        $this->assertTrue( wp_schedule_single_event( $ts1, $hook, $args ) );
     678
     679        // Events over ten minutes should work.
     680        $this->assertTrue( wp_schedule_single_event( $ts2, $hook, $args ) );
     681        $this->assertTrue( wp_schedule_single_event( $ts3, $hook, $args ) );
     682    }
    599683}
Note: See TracChangeset for help on using the changeset viewer.