0

I'm writing a plugin which collects a pretty large feed. From each feed item a custom post will be created. The feed can also contain images for each item that is uploaded from the remote url. Processing the entire feed can take some time and since I would like to do the processing in the background I am looking into cron as a way to achieve this. Currently I'm doing something like this:

class Cron_Task {
  const DEFAULT_TASK_INTERVAL = 30;

  public function __construct($taskName, $data) {
    add_filter('cron_schedules', array($this, 'addSchedule'));
    add_action('cron_task', 'Cron_Task::run');
    if (!wp_next_scheduled('cron_task', array($taskName))) {
      // Store data in cache.
      set_transient($taskName, serialize($data));
      wp_schedule_event(time(), $taskName, 'cron_task', array($taskName));
    }
  }

  public static function processItem($data) {
    // Execute long running task.
  }

  public function addSchedule($schedules) {
    $schedules['crontask'] = array(
      'interval' => self::DEFAULT_TASK_INTERVAL,
      'display' => __('Cron_Task'),
    );
    return $schedules;
  }

  public static function run($taskName) {
    // Get data from cache.
    $data = unserialize(get_transient($taskName));
    if ($data) {
      $item = array_shift($data);
      // Process one item.
      self::processItem($item);

      // Check if we have processed all items.
      if (!count($data)) {
        delete_transient($taskName);
        wp_clear_scheduled_hook('cron_task', array($taskName));
      } else {
        // Update the items to process.
        set_transient($taskName, serialize($data));
      }
    }

  }
}

// When a feed is collected we create a cron_task to be run every 30 seconds.
if (get_feed()) {
  $cronTask = new Cron_Task('testEvent', $feed);
}

The above sort of works, but I think there must be a better way of doing this kind of thing? For instance, since the configured cron tasks will only be run on page load, the task might not be run at the desired interval if there are no visitors.

6
  • if there is no traffic why do you care that the site is not up to date? (serious question, not trying to troll) Mar 13, 2018 at 10:22
  • @MarkKaplun - This is more a question how to perform async tasks at certain intervals in wordpress. I only wrote that there might be no visitors to pin point the problem in this case. Mar 13, 2018 at 10:50
  • if you write a plugin, wordpress cron is the only tool you can be sure to be there. @swissspidy answer is good way to avoid the "no traffic" issue, but it should be fun to make site owners to properly configure it, and totally rare for them to even have access to wpcli Mar 13, 2018 at 10:55
  • .... and as I commented of his answer I believe the best way to go is with you supplying wpcli integration in your plugin. and then, those that can, will be able to trigger the update any way they will want. Mar 13, 2018 at 10:57
  • @MarkKaplun - If you simply would like to call wp-cron.php at a regular interval then the simplest way would be to just add a new cron job using crontab -e =) This question is like I said more about a good way to implement async tasks, like this project: github.com/techcrunch/wp-async-task Mar 13, 2018 at 11:09

2 Answers 2

1

For instance, since the configured cron tasks will only be run on page load, the task might not be run at the desired interval if there are no visitors.

To prevent this, you need to use your operating system's task scheduler.

For this, you need to define define('DISABLE_WP_CRON', true); in your wp-config.php file. After that, you'd add a crontab configuration like this:

/10 * * * * curl http://YOUR_SITE_URL/wp-cron.php > /dev/null 2>&1

This configuration would call http://YOUR_SITE_URL/wp-cron.php every ten minutes. Tasks that are scheduled at this point will be executed.

You can also use WP-CLI for that:

*/10 * * * * cd /var/www/example.com/htdocs; wp cron event run --due-now > /dev/null 2>&1
2
  • 2
    The problem is that if the processing is hard, and takes a long time you will have to split the handling, and if you split it to let say 6 segments, the "import" will take an hour. OTOH wp-cli is much better option to escape the web server limitations, but instead of fighting with wordpress cron I would just implement a command for wp-cli Mar 13, 2018 at 10:26
  • 2
    hmmm and since he is doing a plugin, probably implementing any of this options is not up to him. Mar 13, 2018 at 10:30
1

I had a similar problem to solve and, as it's plugin development, messing with wp-config.php or using WP-CLI are not options. I resolved this as follows.

My scheduled task runs a function that invokes an asynchronous action, via wp_remote_post.

if ( ! wp_next_scheduled( 'my_long_running_event' ) ) {
    wp_schedule_event( $start_time, $recurrence, 'my_long_running_event' );
}

add_action( 'my_long_running_event', 'invoke_my_long_running_function' );
function invoke_my_long_running_function() {
    $nonce = wp_create_nonce( 'my_nonce' . 'my_action' );
    $url = admin_url( 'admin-post.php' );
    $args = array(
        'method'      => 'POST',
        'timeout'     => 5,
        'redirection' => 5,
        'blocking'    => false,
        'headers'     => array(),
        'body'        => array(
            'action'   => 'my_long_running_action',
            'my_nonce' => $nonce,
        ),
    );
    return wp_remote_post( $url, $args );
}

The most important part is 'blocking' => false because this runs the action asynchronously.

The use of a nonce is optional but helps prevent the long-running function being invoked directly. If you ever need to call it in additional to the schedule, call your equivalent of invoke_my_long_running_function() instead.

Then you need an admin_post action hook to actually/finally run your function.

add_action( 'admin_post_nopriv_my_long_running_action', 'my_long_running_function' );

The use of nopriv is not ideal but the scheduled job is not authenticated, so the nopriv is essential, or the action won't run.

Of course, you also need your equivalent of my_long_running_function.

function my_long_running_function() {
    // Do the heavy work here.
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.