PHP Fibers: Cooperative Concurrency for WordPress Background Tasks

PHP 8.1 introduced Fibers — a low-level cooperative concurrency primitive that lets you pause execution of a function, yield control back to the caller, and resume later. While PHP remains single-threaded, Fibers unlock patterns like async I/O coordination, coroutine-style generators, and lightweight task queues that were previously impossible without forking or external queues.

Problem: WordPress background processing — sending emails, generating reports, syncing with external APIs — runs synchronously in PHP or through WP-Cron, blocking the response or missing execution windows on low-traffic sites.

Solution: Use PHP Fibers (PHP 8.1+) to implement cooperative concurrency within a single request: start a Fiber for each background task, suspend with Fiber::suspend() at I/O boundaries, and resume them in turn from the event loop. For truly async processing, combine with a queue system (Redis List + a dedicated worker process running a PHP Fiber scheduler).


The examples below show the basic Fiber API, a coroutine-style pipeline that interleaves three tasks, and a practical WordPress pattern for processing batches of posts inside a Fiber to avoid timeout issues in long-running admin actions.


<?php
// ── 1. Basic Fiber API ────────────────────────────────────────────────────
$fiber = new Fiber( function (): void {
    $value = Fiber::suspend( 'first' );   // pause; send 'first' to caller
    echo "Resumed with: $value\n";        // caller sends 'hello' back
    Fiber::suspend( 'second' );
    echo "Fiber finished\n";
} );

$first  = $fiber->start();               // 'first'
$second = $fiber->resume( 'hello' );     // 'second'
$fiber->resume();                        // fiber finishes

// ── 2. Coroutine-style task runner ────────────────────────────────────────
function makeTask( string $name, int $steps ): Fiber {
    return new Fiber( function () use ( $name, $steps ): void {
        for ( $i = 1; $i <= $steps; $i++ ) {
            echo "$name step $i\n";
            Fiber::suspend();   // yield to the scheduler
        }
    } );
}

$tasks = [
    makeTask( 'A', 3 ),
    makeTask( 'B', 2 ),
    makeTask( 'C', 4 ),
];

// Start all tasks
foreach ( $tasks as $t ) { $t->start(); }

// Round-robin until all are done
$running = true;
while ( $running ) {
    $running = false;
    foreach ( $tasks as $t ) {
        if ( ! $t->isTerminated() ) {
            $t->resume();
            $running = true;
        }
    }
}

// ── 3. WordPress: batch-process posts without timing out ─────────────────
add_action( 'wp_ajax_reindex_posts', function () {
    check_ajax_referer( 'reindex_nonce', 'nonce' );

    $ids = get_posts( [ 'fields' => 'ids', 'numberposts' => -1 ] );

    $fiber = new Fiber( function () use ( $ids ): void {
        $batch = [];
        foreach ( $ids as $id ) {
            $batch[] = $id;
            if ( count( $batch ) === 50 ) {
                process_batch( $batch );   // custom indexing function
                $batch = [];
                Fiber::suspend( count( $batch ) );   // yield progress
            }
        }
        if ( $batch ) {
            process_batch( $batch );
        }
    } );

    $fiber->start();
    while ( ! $fiber->isTerminated() ) {
        $fiber->resume();
        // Optionally: echo progress to client via ob_flush() / flush()
    }

    wp_send_json_success( [ 'indexed' => count( $ids ) ] );
} );


NOTE: Fibers do not add true parallelism — they are cooperative, meaning only one fiber runs at a time; their value is structured control flow and the ability to integrate with event-loop libraries like ReactPHP or Revolt, not raw CPU parallelism.