Extensions

Pogo

Request-scoped parallel PHP jobs for fan-out and fan-in work.

Overview

Pogo runs independent PHP jobs in FrankenPHP extension worker pools while the original request waits for their results.

It is useful for fan-out/fan-in work such as:

  • independent API calls,
  • independent computations,
  • response fragments that can be built in parallel.

Pogo is not a queue. Jobs must complete within the request lifecycle.

Status and fit

Pogo is experimental. Use it for request-scoped parallelism where failures can safely fail the request or be retried by the caller.

Use it when:

  • Work is independent and can run in parallel.
  • Each job is expected to finish quickly enough for an HTTP request.
  • You want separate worker pools for external APIs, CPU work, or critical paths.

Avoid it when:

  • You need persistence, retry, delay, or cancellation.
  • You need a long-running task runner.
  • You need an event loop or fiber abstraction.

Build

Compile the module into FrankenPHP:

Terminal
xcaddy build \
  --with github.com/dunglas/frankenphp@v1.12.3 \
  --with github.com/dunglas/frankenphp/caddy@v1.12.3 \
  --with github.com/y-l-g/pogo/module@main

Install the PHP package:

Terminal
composer require pogo/pogo

Caddy configuration

Configure Pogo as a Caddy global option. A default pool is required.

Caddyfile
{
  frankenphp

  pogo {
    pool default {
      worker public/pogo-worker.php
      num_threads 8
      max_wait 30s
    }

    pool external_api {
      worker public/pogo-worker.php
      num_threads 16
      max_wait 10s
    }

    pool cpu {
      worker public/pogo-worker.php
      num_threads 4
      max_wait 60s
    }
  }
}

Pool directives:

DirectiveRequiredDescription
workerYesPHP worker script.
num_threadsNoFrankenPHP worker thread count.
max_waitNoMaximum wait while sending a job to the worker pool. Defaults to 30s.

Application integration

Create a job class that implements Pogo\JobInterface:

app/Pogo/FetchPrice.php
<?php

namespace App\Pogo;

use Pogo\JobInterface;

final class FetchPrice implements JobInterface
{
    public function handle(array $args): mixed
    {
        return [
            'sku' => $args['sku'],
            'price' => 42,
        ];
    }
}

Create public/pogo-worker.php by copying the package worker or by writing a custom worker that accepts the same payload and response envelope.

The default worker expects:

['class' => App\Pogo\FetchPrice::class, 'args' => ['sku' => 'A-100']]

and returns:

['ok' => true, 'result' => $value]
['ok' => false, 'error' => 'message']

Dispatch and await jobs inside a request:

$price = pogo_dispatch(App\Pogo\FetchPrice::class, ['sku' => $sku], 'external_api');
$stock = pogo_dispatch(App\Pogo\FetchStock::class, ['sku' => $sku], 'external_api');
$tax = pogo_dispatch(App\Pogo\CalculateTax::class, ['sku' => $sku], 'cpu');

return [
    'price' => pogo_await($price, 2.0),
    'stock' => pogo_await($stock, 2.0),
    'tax' => pogo_await($tax, 2.0),
];

PHP API

pogo_dispatch(string $class, array $args = [], string $pool = 'default'): int;
pogo_await(int $handle, float $timeout = 5.0): mixed;
pogo_pool_size(string $pool = 'default'): int;

pogo_await() throws RuntimeException for invalid handles, timeouts, worker failures, and job exceptions.

Arguments and return values must be JSON-compatible. Resources, closures, cyclic data, and unserializable objects are unsupported.

Operations

  • Keep Pogo jobs short enough for the calling request timeout.
  • Use separate pools to isolate slow external APIs from CPU-heavy jobs.
  • Size num_threads according to workload type and available CPU.
  • Treat job failures as request failures unless your application handles partial results.

Troubleshooting

  • Invalid or unknown job class: confirm the class is autoloadable inside the worker.
  • Job must implement Pogo\JobInterface: update the class or use a custom worker.
  • Timeouts from pogo_await(): increase the await timeout or reduce job runtime.
  • Pool missing: confirm the pool is defined in the pogo Caddy block and the binary includes the module.