Skip to main content

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

  1. When a form is submitted, actions can be dispatched to the queue instead of running synchronously.
  2. WP Cron runs the cf_process_queued_actions hook every 5 minutes.
  3. The queue processor claims a batch of pending items and processes them.
  4. 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

Related