WordPress.org

Make WordPress Core

Ticket #49693: 49693-4.patch

File 49693-4.patch, 7.2 KB (added by aidvu, 5 months ago)

Approach with time based limiter for recurring events.

  • src/wp-includes/cron.php

     
    220220        }
    221221
    222222        $key = md5( serialize( $event->args ) );
     223        $crons = (array) _get_cron_array();
     224
     225        // Skip the duplicate check if we're doing cron.
     226        if ( ! wp_doing_cron() ) {
     227                // Always allow 24 occurrences per interval, even if it's an event with long $interval.
     228                $allowed_occurrences = 24;
     229                if ( 24 < DAY_IN_SECONDS / $event->interval ) {
     230                        // Set a per-day limit based on the interval
     231                        $allowed_occurrences = DAY_IN_SECONDS / $event->interval;
     232                }
     233                $check_interval = DAY_IN_SECONDS;
     234                if ( $check_interval < $event->interval ) {
     235                        $check_interval = $event->interval;
     236                }
     237
     238                $min_timestamp = $event->timestamp - ( $check_interval / 2 );
     239                $max_timestamp = $event->timestamp + ( $check_interval / 2 );
     240                $occurrences = 0;
     241
     242                foreach ( $crons as $event_timestamp => $cron ) {
     243                        if ( $event_timestamp < $min_timestamp ) {
     244                                continue;
     245                        }
     246                        if ( $event_timestamp > $max_timestamp ) {
     247                                break;
     248                        }
     249                        if ( isset( $cron[ $event->hook ][ $key ] ) && $cron[ $event->hook ][ $key ]['interval'] === $event->interval ) {
     250                                $occurrences++;
     251                                continue;
     252                        }
     253                }
     254
     255                if ( $allowed_occurrences <= $occurrences ) {
     256                        return false;
     257                }
     258        }
    223259
    224         $crons = _get_cron_array();
    225260        $crons[ $event->timestamp ][ $event->hook ][ $key ] = array(
    226261                'schedule' => $event->schedule,
    227262                'args'     => $event->args,
    228263                'interval' => $event->interval,
    229264        );
    230265        uksort( $crons, 'strnatcasecmp' );
     266
    231267        return _set_cron_array( $crons );
    232268}
    233269
  • tests/phpunit/tests/cron.php

     
    534534         * @ticket 45976.
    535535         */
    536536        function test_get_scheduled_event_recurring() {
    537                 $hook     = __FUNCTION__;
    538                 $args     = array( 'arg1' );
    539                 $ts_late  = strtotime( '+30 minutes' );
    540                 $ts_next  = strtotime( '+3 minutes' );
    541                 $schedule = 'hourly';
    542                 $interval = HOUR_IN_SECONDS;
     537                $hook         = __FUNCTION__;
     538                $args         = array( 'arg1' );
     539                $ts_late      = strtotime( '+30 minutes' );
     540                $ts_next      = strtotime( '+3 minutes' );
     541                $schedule_one = 'hourly';
     542                $schedule_two = 'daily';
     543                $interval_one = HOUR_IN_SECONDS;
     544                $interval_two = DAY_IN_SECONDS;
    543545
    544546                $expected1 = (object) array(
    545547                        'hook'      => $hook,
    546548                        'timestamp' => $ts_late,
    547                         'schedule'  => $schedule,
     549                        'schedule'  => $schedule_one,
    548550                        'args'      => $args,
    549                         'interval'  => $interval,
     551                        'interval'  => $interval_one,
    550552                );
    551553
    552554                $expected2 = (object) array(
    553555                        'hook'      => $hook,
    554556                        'timestamp' => $ts_next,
    555                         'schedule'  => $schedule,
     557                        'schedule'  => $schedule_two,
    556558                        'args'      => $args,
    557                         'interval'  => $interval,
     559                        'interval'  => $interval_two,
    558560                );
    559561
    560562                // Schedule late running event.
    561                 wp_schedule_event( $ts_late, $schedule, $hook, $args );
     563                wp_schedule_event( $ts_late, $schedule_one, $hook, $args );
    562564                // Schedule next running event.
    563                 wp_schedule_event( $ts_next, $schedule, $hook, $args );
     565                wp_schedule_event( $ts_next, $schedule_two, $hook, $args );
    564566
    565567                // Late running, timestamp specified.
    566568                $this->assertEquals( $expected1, wp_get_scheduled_event( $hook, $args, $ts_late ) );
     
    680682                $this->assertTrue( wp_schedule_single_event( $ts2, $hook, $args ) );
    681683                $this->assertTrue( wp_schedule_single_event( $ts3, $hook, $args ) );
    682684        }
     685
     686        /**
     687         * Recurring events have a max event limiter. Make sure it's honored properly.
     688         *
     689         * @ticket 49693
     690         */
     691        function test_duplicate_recurring_event() {
     692                $hook     = __FUNCTION__;
     693                $args     = array( 'arg1' );
     694                $schedule = 'hourly';
     695
     696                // Add the max number of events for an 'hourly' schedule.
     697                $max_events = DAY_IN_SECONDS / HOUR_IN_SECONDS; // Hourly schedule, max events per day is 24.
     698                for ( $i = 0; $i < $max_events; $i++ ) {
     699                        $timestamp_next = strtotime( '+' . ( 60 + $i ) . ' minutes' );
     700                        // Scheduling the same recurring event (hook and args), but with different timestamp should fail.
     701                        $this->assertTrue( wp_schedule_event( $timestamp_next, $schedule, $hook, $args ) );
     702                }
     703                $expected = _get_cron_array();
     704
     705                // Scheduling the event $max_events + 1 time should fail.
     706                $this->assertFalse( wp_schedule_event( strtotime( '+120 minutes' ), $schedule, $hook, $args ) );
     707
     708                // Check cron option is unchanged.
     709                $this->assertEquals( $expected, _get_cron_array() );
     710                $this->assertCount( $max_events, _get_cron_array() );
     711        }
     712
     713        /**
     714         * Recurring events that have the same hook but different args or schedule are allowed.
     715         *
     716         * @ticket 49693
     717         */
     718        function test_not_duplicate_recurring_event() {
     719                $hook         = __FUNCTION__;
     720                $args_one     = array( 'arg1' );
     721                $args_two     = array( 'arg2' );
     722                $timestamp    = strtotime( '+60 minutes' );
     723                $schedule_one = 'hourly';
     724                $schedule_two = 'daily';
     725
     726                // Schedule recurring event.
     727                $this->assertNotFalse( wp_schedule_event( $timestamp, $schedule_one, $hook, $args_one ) );
     728
     729                // Schedule recurring event as above, but with different schedule is allowed.
     730                $this->assertNotFalse( wp_schedule_event( $timestamp, $schedule_two, $hook, $args_one ) );
     731
     732                // Schedule recurring event as above, but with different args is allowed.
     733                $this->assertNotFalse( wp_schedule_event( $timestamp, $schedule_one, $hook, $args_two ) );
     734        }
     735
     736        /**
     737         * Make sure that rescheduling still works with the recurring event limiter when running CRON.
     738         *
     739         * @ticket 49693
     740         */
     741        function test_reschedule_recurring_event() {
     742                $hook   = __FUNCTION__;
     743                $ts_one = strtotime( '+30 minutes' );
     744
     745                // Pretend CRON is running
     746                add_filter( 'wp_doing_cron', '__return_true' );
     747
     748                // Confirm there's no events.
     749                $this->assertEmpty( _get_cron_array() );
     750
     751                // Add an event.
     752                $this->assertTrue( wp_schedule_event( $ts_one, 'hourly', $hook ) );
     753
     754                // Reschedule it.
     755                $this->assertNotFalse( wp_reschedule_event( $ts_one, 'hourly', $hook ) );
     756
     757                // Make sure the original event is still there.
     758                $this->assertNotFalse( wp_get_scheduled_event( $hook, array(), $ts_one ) );
     759
     760                // Make sure the rescheduled event is also there.
     761                $this->assertEquals( 2, count( _get_cron_array() ) );
     762        }
     763
     764        /**
     765         * Make sure that the limiter applies to event rescheduling if we're not running CRON.
     766         *
     767         * @ticket 49693
     768         */
     769        function test_not_reschedule_recurring_event() {
     770                $hook     = __FUNCTION__;
     771                $schedule = 'hourly';
     772
     773                // Add the max number of events for an 'hourly' schedule.
     774                $max_events = DAY_IN_SECONDS / HOUR_IN_SECONDS; // Hourly schedule, max events per day is 24.
     775                for ( $i = 0; $i < $max_events; $i++ ) {
     776                        $timestamp_next = strtotime( '+' . ( 60 + $i ) . ' minutes' );
     777                        // Scheduling the same recurring event (hook and args), but with different timestamp should fail.
     778                        $this->assertTrue( wp_schedule_event( $timestamp_next, $schedule, $hook ) );
     779                }
     780                $expected = _get_cron_array();
     781
     782                // Rescheduling the event fails because of the limiter.
     783                $this->assertFalse( wp_reschedule_event( strtotime( '+120 minutes' ), $schedule, $hook ) );
     784
     785                // Check cron option is unchanged.
     786                $this->assertEquals( $expected, _get_cron_array() );
     787                $this->assertCount( $max_events, _get_cron_array() );
     788        }
    683789}