A robust scheduling library for use in WordPress plugins.
Action Scheduler uses a WordPress custom post type, creatively named scheduled-action
, to store the hook name, arguments and scheduled date for an action that should be triggered at some time in the future.
The scheduler will run every minute by attaching itself as a callback to the 'action_scheduler_run_schedule'
hook, which is scheduled using WordPress's built-in WP-Cron system.
When triggered, Action Scheduler will check for posts of the scheduled-action
type that have a post_date
at or before this point in time i.e. actions scheduled to run now or at sometime in the past.
If there are actions to be processed, Action Scheduler will stake a unique claim for a batch of 20 actions and begin processing that batch. The PHP process spawned to run the batch will then continue processing batches of 20 actions until it times out or exhausts available memory.
If your site has a large number of actions scheduled to run at the same time, Action Scheduler will process more than one batch at a time. Specifically, when the 'action_scheduler_run_schedule'
hook is triggered approximately one minute after the first batch began processing, a new PHP process will stake a new claim to a batch of actions which were not claimed by the previous process. It will then begin to process that batch.
This will continue until all actions are processed using a maximum of 5 concurrent queues.
Before processing a batch, the scheduler will remove any existing claims on actions which have been sitting in a queue for more than five minutes.
Action Scheduler will also trash any actions which were completed more than a month ago.
If an action runs for more than 5 minutes, Action Scheduler will assume the action has timed out and will mark it as failed. However, if all callbacks attached to the action were to successfully complete sometime after that 5 minute timeout, its status would later be updated to completed.
Events for each action will be also logged in the comments table.
The events logged by default include when an action:
- is created
- starts
- completes
- fails
Actions can also be grouped together using a custom taxonomy named action-group
.
Action Scheduler has a built in administration screen for monitoring, debugging and manually triggering scheduled actions.
The administration interface is accesible through both:
- Tools > Scheduled Actions
- WooCommerce > Status > Scheduled Actions, when WooCommerce is installed.
Among other tasks, from the admin screen you can:
- run a pending action
- view the scheduled actions with a specific status, like the all actions which have failed or are in-progress (https://cldup.com/NNTwE88Xl8.png).
- view the log entries for a specific action to find out why it failed.
- sort scheduled actions by hook name, scheduled date, claim ID or group name.
Still have questions? Check out the FAQ below.
Action Scheduler has custom WP CLI commands available for processing actions.
For large sites, WP CLI is a much better choice for running queues of actions than the default WP Cron runner. These are some common cases where WP CLI is a better option:
- long-running tasks - Tasks that take a significant amount of time to run
- large queues - A large number of tasks will naturally take a longer time
- other plugins with extensive WP Cron usage - WP Cron's limited resources are spread across more tasks
With a regular web request, you may have to deal with script timeouts enforced by hosts, or other restraints that make it more challenging to run Action Scheduler tasks. Utilizing WP CLI to run commands directly on the server give you more freedom. This means that you typically don't have the same constraints of a normal web request.
If you choose to utilize WP CLI exclusively, you can disable the normal WP CLI queue runner by installing the Action Scheduler - Disable Default Queue Runner plugin. Note that if you do this, you must run Action Scheduler via WP CLI or another method, otherwise no scheduled actions will be processed.
These are the commands available to use with Action Scheduler:
-
action-scheduler run
Options:
--batch-size
- This is the number of actions to run in a single batch. The default is100
.--batches
- This is the number of batches to run. Using 0 means that batches will continue running until there are no more actions to run.--hooks
- Process only actions with specific hook or hooks, like'woocommerce_scheduled_subscription_payment'
. By default, actions with any hook will be processed. Define multiple hooks as a comma separated string (without spaces), e.g.--hooks=woocommerce_scheduled_subscription_trial_end,woocommerce_scheduled_subscription_payment,woocommerce_scheduled_subscription_expiration
--group
- Process only actions in a specific group, like'woocommerce-memberships'
. By default, actions in any group (or no group) will be processed.--force
- By default, Action Scheduler limits the number of concurrent batches that can be run at once to ensure the server does not get overwhelmed. Using the--force
flag overrides this behavior to force the WP CLI queue to run.
The best way to get a full list of commands and their available options is to use WP CLI itself. This can be done by running wp action-scheduler
to list all Action Scheduler commands, or by including the --help
flag with any of the individual commands. This will provide all relevant parameters and flags for the command.
The --group
and --hooks
options should be used with caution if you have an implicit dependency between scheduled actions based on their schedule.
For example, consider two scheduled actions for the same subscription:
scheduled_payment
scheduled for2015-11-13 00:00:00
andscheduled_expiration
scheduled for2015-11-13 00:01:00
.
Under normal conditions, Action Scheduler will ensure the scheduled_payment
action is run before the scheduled_expiration
action. Becuase that's how they are scheduled.
However, when using the --hooks
option, the scheduled_payment
and scheduled_expiration
actions will be processed in separate queues. As a result, this dependency is not guaranteed.
For example, consider a site with both:
- 100,000
scheduled_payment
actions, scheduled for2015-11-13 00:00:00
- 100
scheduled_expiration
actions, scheduled for2015-11-13 00:01:00
If two queue runners are running alongside each other with each runner dedicated to just one of these hooks, the queue runner handling expiration hooks will complete the processing of the expiration hooks more quickly than the queue runner handling all the payment actions.
Because of this, the --group
and --hooks
options should be used with caution to avoid processing actions with an implicit dependency based on their schedule in separate queues.
Being able to run queues for specific hooks or groups of actions is valuable at scale. Why? Because it means you can restrict the concurrency for similar actions.
For example, let's say you have 300,000 actions queued up comprised of:
- 100,000 renewals payments
- 100,000 email notifications
- 100,000 membership status updates
Action Scheduler's default WP Cron queue runner will process them all together. e.g. when it claims a batch of actions, some may be emails, some membership updates and some renewals.
When you add concurrency to that, you can end up with issues. For example, if you have 3 queues running, they may all be attempting to process similar actions at the same time, which can lead to querying the same database tables with similar queries. Depending on the code/queries running, this can lead to database locks or other issues.
If you can batch based on each action's group, then you can improve performance by processing like actions consecutively, but still processing the full set of actions concurrently.
For example, if one queue is created to process emails, another to process membership updates, and another to process renewal payments, then the same queries won't be run at the same time, and 3 separate queues will be able to run more efficiently.
The WP CLI runner can achieve this using the --group
option.
The Action Scheduler API functions are designed to mirror the WordPress WP-Cron API functions.
Functions return similar values and accept similar arguments to their WP-Cron counterparts. The notable differences are:
wc_schedule_single_action()
&wc_schedule_recurring_action()
will return the post ID of the scheduled action rather than boolean indicating whether the event was scheduledwc_schedule_recurring_action()
takes an interval in seconds as the recurring interval rather than an arbitrary stringwc_schedule_single_action()
&wc_schedule_recurring_action()
can accept a$group
parameter to group different actions for the one plugin together.- the
wp_
prefix is substituted withwc_
and the termevent
is replaced withaction
Schedule an action to run one time.
<?php wc_schedule_single_action( $timestamp, $hook, $args, $group ); ?>
- $timestamp (integer)(required) The Unix timestamp representing the date you want the action to run. Default: none.
- $hook (string)(required) Name of the action hook. Default: none.
- $args (array) Arguments to pass to callbacks when the hook triggers. Default:
array()
. - $group (array) The group to assign this job to. Default: ''.
(integer) the action's ID in the posts table.
Schedule an action to run repeatedly with a specified interval in seconds.
<?php wc_schedule_recurring_action( $timestamp, $interval_in_seconds, $hook, $args, $group ); ?>
- $timestamp (integer)(required) The Unix timestamp representing the date you want the action to run. Default: none.
- $interval_in_seconds (integer)(required) How long to wait between runs. Default: none.
- $hook (string)(required) Name of the action hook. Default: none.
- $args (array) Arguments to pass to callbacks when the hook triggers. Default:
array()
. - $group (array) The group to assign this job to. Default: ''.
(integer) the action's ID in the posts table.
Schedule an action that recurs on a cron-like schedule.
<?php wc_schedule_cron_action( $timestamp, $schedule, $hook, $args, $group ); ?>
- $timestamp (integer)(required) The Unix timestamp representing the date you want the action to run. Default: none.
- $schedule (string)(required) $schedule A cron-link schedule string, see http://en.wikipedia.org/wiki/Cron. Default: none.
- $hook (string)(required) Name of the action hook. Default: none.
- $args (array) Arguments to pass to callbacks when the hook triggers. Default:
array()
. - $group (array) The group to assign this job to. Default: ''.
(integer) the action's ID in the posts table.
Cancel the next occurrence of a job.
<?php wc_unschedule_action( $hook, $args, $group ); ?>
- $hook (string)(required) Name of the action hook. Default: none.
- $args (array) Arguments to pass to callbacks when the hook triggers. Default:
array()
. - $group (array) The group to assign this job to. Default: ''.
(null)
Returns the next timestamp for a scheduled action.
<?php wc_next_scheduled_action( $hook, $args, $group ); ?>
- $hook (string)(required) Name of the action hook. Default: none.
- $args (array) Arguments to pass to callbacks when the hook triggers. Default:
array()
. - $group (array) The group to assign this job to. Default: ''.
(integer|boolean) The timestamp for the next occurrence, or false if nothing was found.
Find scheduled actions.
<?php wc_get_scheduled_actions( $args, $return_format ); ?>
- $args (array) Arguments to search and filter results by. Possible arguments, with their default values:
'hook' => ''
- the name of the action that will be triggered'args' => NULL
- the args array that will be passed with the action'date' => NULL
- the scheduled date of the action. Expects a DateTime object, a unix timestamp, or a string that can parsed with strtotime().'date_compare' => '<=
' - operator for testing "date". accepted values are '!=', '>', '>=', '<', '<=', '=''modified' => NULL
- the date the action was last updated. Expects a DateTime object, a unix timestamp, or a string that can parsed with strtotime().'modified_compare' => '<='
- operator for testing "modified". accepted values are '!=', '>', '>=', '<', '<=', '=''group' => ''
- the group the action belongs to'status' => ''
- ActionScheduler_Store::STATUS_COMPLETE or ActionScheduler_Store::STATUS_PENDING'claimed' => NULL
- TRUE to find claimed actions, FALSE to find unclaimed actions, a string to find a specific claim ID'per_page' => 5
- Number of results to return'offset' => 0
'orderby' => 'date'
- accepted values are 'hook', 'group', 'modified', or 'date''order' => 'ASC'
- $return_format (string) The format in which to return the scheduled actions: 'OBJECT', 'ARRAY_A', or 'ids'. Default: 'OBJECT'.
(array) Array of the actions matching the criteria specified with $args
.
By default, Action Scheduler will process a minimum of 1,200 actions per hour. On servers which allow long running PHP processes, this will be significantly higher as processes will be able loop over queues indefinitely.
The batch size and number of concurrent queues that may be processed simultaneously is low by default to ensure the scheduler runs on low powered servers; however, you can configure these settings to increase performance on your site.
By default, Action Scheduler will claim a batch of 20 actions. This small batch size is to minimise the risk of causing a fatal error due to memory exhaustion.
If you know the callbacks attached to your actions use very little memory, or you've tested the number of actions you can process before memory limits are exceeded, you can increase the batch size using the 'action_scheduler_queue_runner_batch_size'
filter.
For example, to increase the batch size to 100, we can use the following function:
<?php
function eg_increase_action_scheduler_batch_size( $batch_size ) {
return 100;
}
add_filter( 'action_scheduler_queue_runner_batch_size', 'eg_increase_action_scheduler_batch_size' );
?>
By default, Action Scheduler will run up to 5 concurrent batches of actions. This is to prevent consuming all the available connections or processes on your webserver.
However, your server may allow a large number of connection, for example, because it has a high value for Apache's MaxClients
setting or PHP-FPM's pm.max_children
setting.
If this is the case, you can use the 'action_scheduler_queue_runner_concurrent_batches'
filter to increase the number of conncurrent batches allowed, and therefore speed up processing large numbers of actions scheduled to be processed simultaneously.
For example, to increase the allowed number of concurrent queues to 25, we can use the following code:
<?php
function eg_increase_action_scheduler_concurrent_batches( $concurrent_batches ) {
return 25;
}
add_filter( 'action_scheduler_queue_runner_concurrent_batches', 'eg_increase_action_scheduler_concurrent_batches' );
?>
Is it safe to release Action Scheduler in my plugin? Won't its functions conflict with another copy of the library?
Action Scheduler is designed to be used and released in plugins. It avoids redeclaring public API functions when more than one copy of the library is being loaded by different plugins. It will also load only the most recent version of itself (by checking registered versions after all plugins are loaded on the 'plugins_loaded'
hook).
To use it in your plugin, simply require the action-scheduler/action-scheduler.php
file. Action Scheduler will take care of the rest.
By default, Action Scheduler is initiated by WP-Cron. However, it has no dependency on the WP-Cron system. You can initiate the Action Scheduler queue in other ways with just one or two lines of code.
For example, you can start a queue directly by calling:
ActionScheduler::runner()->run();
Or trigger the 'action_scheduler_run_queue'
hook and let Action Scheduler do it for you:
do_action( 'action_scheduler_run_queue' );
Further customization can be done by extending the ActionScheduler_Abstract_QueueRunner
class to create a custom Queue Runner. For an example of a customized queue runner, see the ActionScheduler_WPCLI_QueueRunner
, which is used when running WP CLI.
Want to create some other method for initiating Action Scheduler? Open a new issue, we'd love to help you with it.
By default, Action Scheduler is designed to work alongside WP-Cron and not change any of its behaviour. This helps avoid unexpectedly overriding WP-Cron on sites installing your plugin, which may have nothing to do with WP-Cron.
However, we can understand why you might want to replace WP-Cron completely in environments within you control, especially as it gets you the advantages of Action Scheduler. This should be possible without too much code.
You could use the 'schedule_event'
hook in WordPress to use Action Scheduler for only newly scheduled WP-Cron jobs and map the $event
param to Action Scheduler API functions.
Alternatively, you can use a combination of the 'pre_update_option_cron'
and 'pre_option_cron'
hooks to override all new and previously scheduled WP-Cron jobs (similar to the way Cavalcade does it).
If you'd like to create a plugin to do this automatically and want to share your work with others, open a new issue to let us know, we'd love to help you with it.
Of course! Action Scheduler data storage is completely swappable, and always has been.
You can store scheduled actions in custom tables in the WordPress site's database. Some sites using it already are. You can actually store them anywhere for that matter, like in a remote storage service from Amazon Web Services.
To implement a custom store:
- extend the abstract
ActionScheduler_Store
class, being careful to implement each of its methods - attach a callback to
'action_scheduler_store_class'
to tell Action Scheduler your class is the one which should be used to manage storage, e.g.
function eg_define_custom_store( $existing_storage_class ) {
return 'My_Radical_Action_Scheduler_Store';
}
add_filter( 'action_scheduler_store_class', 'eg_define_custom_store', 10, 1 );
Take a look at the ActionScheduler_wpPostStore
class for an example implementation of ActionScheduler_Store
.
If you'd like to create a plugin to do this automatically and release it publicly to help others, open a new issue to let us know, we'd love to help you with it.
Note: we're also moving Action Scheduler itself to use custom tables for better scalability.
Of course! Action Scheduler's logger is completely swappable, and always has been. You can also customise where logs are stored, and the storage mechanism.
To implement a custom logger:
- extend the abstract
ActionScheduler_Logger
class, being careful to implement each of its methods - attach a callback to
'action_scheduler_logger_class'
to tell Action Scheduler your class is the one which should be used to manage logging, e.g.
function eg_define_custom_logger( $existing_storage_class ) {
return 'My_Radical_Action_Scheduler_Logger';
}
add_filter( 'action_scheduler_logger_class', 'eg_define_custom_logger', 10, 1 );
Take a look at the ActionScheduler_wpCommentLogger
class for an example implementation of ActionScheduler_Logger
.
Wow, now you're really asking the tough questions. In theory, yes, this is possible. The ActionScheduler_QueueRunner
class, which is responsible for running queues, is swappable via the 'action_scheduler_queue_runner_class'
filter.
Because of this, you can effectively customise queue running however you need. Whether that means tweaking minor things, like not using WP-Cron at all to initiate queues by overriding ActionScheduler_QueueRunner::init()
, or completely changing how and where queues are run, by overriding ActionScheduler_QueueRunner::run()
.
Yes, absolutely! Action Scheduler is actively used on tens of thousands of production sites already. Right now it's responsible for scheduling everything from emails to payments.
In fact, every month, Action Scheduler processes millions of payments as part of the WooCommerce Subscriptions extension.
It requires no setup, and won't override any WordPress APIs (unless you want it to).
Action Scheduler is designed to manage the scheduled actions on a single site. It has no special handling for running queues across multiple sites in a multisite network. That said, because it's storage and Queue Runner are completely swappable, it would be possible to write multisite handling classes to use with it.
If you'd like to create a multisite plugin to do this and release it publicly to help others, open a new issue to let us know, we'd love to help you with it.
This extension is developed and maintained as a collaboration between the teams at Prospress and Flightless.
Collaboration is cool. We'd love to work with you to improve Action Scheduler. Pull Requests welcome.