TransWikia.com

Use cron to create a non blocking task

WordPress Development Asked by Cyclonecode on January 7, 2021

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.

2 Answers

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.
}

Answered by GeeC on January 7, 2021

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

Answered by swissspidy on January 7, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP