Async Actions
Core Forms includes a background action queue (ActionQueue) that processes actions asynchronously via WP Cron. This prevents slow third-party API calls from blocking form submissions.
How It Works
- When a form is submitted, actions can be dispatched to the queue instead of running synchronously.
- WP Cron runs the
cf_process_queued_actionshook every 5 minutes. - The queue processor claims a batch of pending items and processes them.
- Failed actions are automatically retried with exponential backoff.
Architecture
The queue is stored in the wp_cf_action_queue database table:
| Column | Type | Description |
|---|---|---|
id |
INT | Primary key |
form_id |
INT | Form post ID |
submission_id |
INT | Submission ID |
action_type |
VARCHAR(50) | Action type identifier |
action_settings |
LONGTEXT | JSON-encoded action configuration |
status |
VARCHAR(20) | Current status |
priority |
INT | Lower number = higher priority (default: 10) |
attempts |
INT | Number of processing attempts |
max_retries |
INT | Maximum retry attempts (default: 3) |
last_error |
TEXT | Last error message |
scheduled_at |
TIMESTAMP | When to process next (for retries) |
started_at |
TIMESTAMP | When processing began |
completed_at |
TIMESTAMP | When processing finished |
created_at |
TIMESTAMP | When the item was enqueued |
Statuses
| Status | Description |
|---|---|
pending |
Waiting to be processed |
processing |
Currently being processed |
completed |
Successfully processed |
failed |
Failed after all retry attempts |
retrying |
Failed but scheduled for retry |
Enqueuing Actions
use Core_Forms\Workflows\ActionQueue;
// Enqueue an action for background processing
$queue_id = ActionQueue::enqueue(
$action_settings, // Array with 'type' key and action config
$submission, // Submission object
$form, // Form object
10 // Priority (lower = higher priority)
);
Atomic Claim Pattern
The queue processor uses an atomic claim pattern to prevent duplicate processing when cron jobs overlap:
// 1. Generate a unique claim ID
$claim_id = uniqid( 'cf_', true );
// 2. Atomically UPDATE pending/retrying rows with our claim ID
UPDATE wp_cf_action_queue
SET status = 'processing', last_error = '{claim_id}'
WHERE status IN ('pending', 'retrying')
AND (scheduled_at IS NULL OR scheduled_at <= NOW())
ORDER BY priority ASC, created_at ASC
LIMIT 10;
// 3. SELECT only the rows we claimed
SELECT * FROM wp_cf_action_queue
WHERE status = 'processing' AND last_error = '{claim_id}';
This ensures that even if two cron processes run simultaneously, each item is processed exactly once.
Priority-Based Queue
Items are processed in priority order (lower number first), then by creation time:
// High-priority email notification
ActionQueue::enqueue( $email_settings, $submission, $form, 1 );
// Normal-priority webhook
ActionQueue::enqueue( $webhook_settings, $submission, $form, 10 );
// Low-priority analytics sync
ActionQueue::enqueue( $sync_settings, $submission, $form, 50 );
Cron Schedule
The queue processor runs on a custom 5-minute cron schedule:
// Registered schedule
$schedules['cf_every_five_minutes'] = [
'interval' => 300,
'display' => 'Every 5 Minutes',
];
// Cron hook
add_action( 'cf_process_queued_actions', [ $this, 'process_queue' ] );
Queue Stats
Get current queue statistics via the REST API or directly:
$stats = ActionQueue::get_stats();
// Returns:
// [
// 'pending' => 5,
// 'processing' => 0,
// 'completed' => 142,
// 'failed' => 3,
// 'retrying' => 1,
// ]
REST endpoint: GET /wp-json/cf/v1/action-queue/stats