How to make sure that only one wp_cron() runs at a time?

The question:

I have around 20 wp_cron() functions like the following code. Almost all crons run hourly; a few are daily.

if ( ! wp_next_scheduled( 'my_task_hook' ) ) {
  wp_schedule_event( time(), 'hourly', 'my_task_hook' );
}

add_action( 'my_task_hook', 'my_task_function' );

function my_task_function() {
  //Complex Code
}

To increase server performance, and so as not to keep getting server limit messages from hosting companies, I want to make sure that only one cron runs at a given time… Is it possible?


The current accepted answer is great, but I have following question, that’s why I start a bounty for this question.

Please read the accepted answer at first.

Let assume Cron 1 runs, My code of cron2 won’t running because still we are inside the 5 minute or first cron is running still, but because of wp_schedule_event( time(), 'hourly', 'my_task_hook' ); running for cron2 I think WordPress consider as cron2 runs….. So basically code of cron2 code never run….. Or I have misunderstand something?

The Solutions:

Below are the methods you can try. The first solution is probably the best. Try others if the first one doesn’t work. Senior developers aren’t just copying/pasting – they read the methods carefully & apply them wisely to each case.

Method 1

Yes, it is possible…

And to be honest, it’s often very important to do this… WP Scheduler sometimes tends to cause problems, when cron tasks are long…

So how I solve this problem?

I use Transients API to implement semaphores…

Here’s the code:

if ( ! wp_next_scheduled( 'my_task_hook' ) ) {
  wp_schedule_event( time(), 'hourly', 'my_task_hook' );
}

add_action( 'my_task_hook', 'my_task_function' );

function my_task_function() {
  // if some other my_task is already running, stop
  if ( get_transient( 'my_task_function_semaphore' ) ) return;

  // set semaphore for 5 minutes
  set_transient( 'my_task_function_semaphore', true, 5*60 );

  // DO YOUR STUFF

  delete_transient( 'my_task_function_semaphore' );
}

Why do I use transients in this case? Because:

  • They’re part of WP.
  • They’re easy to use and efficient.
  • They won’t cause deadlocks. Let’s say my cron task can get killed (some error may occur, or it runs too long and gets killed, and so on). In such case it won’t delete semaphore, so all future tasks won’t work. Using transients solves this problem, because after some time transient will be deleted.

And what if there are many different actions to do?

So let’s say there are many different cron tasks, that should never run in same time, but we still want them all to run…

If we use the solution with semaphore and use only one semaphore for all of these tasks, then some of them may never run. So what to do then?

In such case you should change your thinking. You don’t have some independent tasks, but a queue of tasks to do. So you should implement it in this way.

So:

  1. You add some kind of queue (you can use an array and store it as option, or add a custom DB table).
  2. You use your currently WP cron hourly event to add tasks to queue.
  3. You add second WP cron tasks which runs much more often and it “eats” tasks from queue one by one. This “eater” task should use semaphore to ensure that only one task runs at a moment.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Comment