Controlling when Action Scheduler runs
When the Action Scheduler library needs to process any waiting tasks, it does so via one or more "queue runners" ... but what actually triggers these queue runners? When and how often do they run, and can this behaviour be tweaked? Generally speaking, there are three ways in which they might be invoked:
- Every minute, via WP Cron (WordPress's own scheduled events system).
- Via asynchronous HTTP requests (thanks to the WP Background Processing library).
- Via custom WP CLI commands.
None of these are mutually exclusive: it's quite possible to use all three, if desired. Out of the box, though, only the first two options are used—which is a pretty reasonable default:
- WP Cron is an integral part of WordPress. Lots of web hosts, particularly those specializing in hosting WordPress sites, will make sure it is being regularly triggered (or will at least have support articles to help site owners configure things). So, this provides some baseline reliability.
- Additional queue runners spawned via async HTTP requests are also used, in case more tasks are waiting than can reasonably be processed within WP Cron sessions. This is also a sort of back-up option, in the event someone has blocked WP Cron from running.
So, this typically provides a decent mix of reliability and throughput. However, a further constraint is that, in effect, only one queue runner is allowed to be active at any given time. Again, this is a pretty reasonable default and prevents too many resources being sucked up. We can however override this with a custom snippet, as follows:
// Allow up to 5 concurrent queue runners.
add_filter( 'action_scheduler_queue_runner_concurrent_batches', fn () => 5 );
Using WP CLI
So far, so good, but where does WP CLI come in? Well, when this utility is available, we can get really creative and exercise a much finer degree of control over when and how waiting tasks are processed. Please refer to the manual for the full range of options, which include things like:
- Specifying which group of tasks should be processed (you might for instance wish to have a dedicated queue runner that focuses only on actions from the payment renewals group).
- Which group of tasks should be excluded (if you take advantage of the previous point, you probably also want a general purpose queue runner that focuses on the remaining task groups).
- How many actions should be processed in each batch.
- Forcing queue runners to run, even if the limit on the number of concurrent batches has been reached.
Combine this with a system scheduler like Cron, or some other supervisor system, and the sky is the limit.
Only using WP CLI
Building on the last section, if you decide to craft a highly tailored execution environment for scheduled actions based on a set of WP CLI commands, you may wish to stop processing actions through the other two channels. Stopping async requests is pretty trivial:
// Do not allow Action Scheduler to trigger queue runners via async HTTP requests.
add_filter( 'action_scheduler_allow_async_request_runner', '__return_false' );
Removing Action Scheduler's WP Cron integration requires just a little more work, because it is designed to continually re-register itself, but can also be done with a snippet:
// Unregister Action Scheduler's WP Cron callback.
add_action( 'action_scheduler_init', function () {
remove_action(
ActionScheduler_QueueRunner::WP_CRON_HOOK,
[ ActionScheduler_QueueRunner::instance(), 'run' ]
);
} );
Final notes:
- At time of writing, Action Scheduler 3.7.1 has just been released. Things may change with future releases.
- The concurrent batch limit applies to batches, not queue runners. In most real world cases, though, we can treat these as being the same thing.
- It may seem like I'm advocating for Action Scheduler to be run only via WP CLI commands hooked into system cron. Not so! Async HTTP-based queue runners have their own benefits, especially in a well-provisioned hosting environment.
- I wrote that the queue runner will be invoked once every minute with WP Cron. Of course, as a rule of thumb, we shouldn't treat this as a guarantee.