spatie/async: A Thin PCNTL Wrapper for Parallel PHP Processes, With a Silent Synchronous Fallback
Easily run code asynchronously
At a glance
- What is it?
- spatie/async is a small PHP library that runs closures and tasks in parallel child processes through the PCNTL extension. This piece covers the pool settings, how errors cross the process boundary, the bootstrapping cost of each child and what happens when the extensions are missing.
- Who is it for?
- spatie/async suits PHP scripts and workers that want to fan work out to a bounded number of child processes with a small API, and where pcntl and posix are available. It is a poor fit where those extensions are missing and parallelism is a hard requirement, because the pool quietly runs tasks one after another instead of failing.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 9 days ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.
Star history
- spatie/async2,807 ★ · -6
| Repository | 2026-09-30 | 2026-11-14 |
|---|---|---|
| spatie/async | 2.8k | 2.8k |
-6 Stars in the last 45 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
Without pcntl and posix the pool runs everything synchronously and says nothing
The library is described as a small wrapper around PHP's PCNTL extension, and the parallelism comes entirely from that extension. If the required extensions, pcntl and posix, are not installed in your current PHP runtime, the Pool automatically falls back to synchronous execution of tasks. The README text shown gives no warning or exception for that case.
The consequence is easy to miss. A script that works in parallel on your laptop can run sequentially on a host whose PHP build lacks those extensions, with the same code and the same output, only slower. If speed or concurrency is the reason you chose the library, check the extensions on every environment you deploy to instead of assuming the pool is doing what you wrote. The Pool class also has a static method related to this fallback, but its description is cut off in the text available here, so read the full README before building on it.
Every child starts from nothing, so a Task exists to do the bootstrapping
Each child process is bootstrapped from nothing. A closure passed to the pool carries no initialised dependency container and no loaded configuration from the parent. That is the reason for the Task class: it has a configure method for setup, such as the dependency container and config loading, and a run method for the real work.
use Spatie\Async\Task;
class MyTask extends Task
{
public function configure()
{
// Setup eg. dependency container, load config,...
}
public function run()
{
// Do the real work here.
}
}
// Add the task to the pool
$pool->add(new MyTask());If you only want to encapsulate logic without a full Task, you can pass an invokable object to the pool instead. The cost to remember is that setup work repeats in every child, so heavy configure code multiplies by the number of tasks you add.
Four pool settings control concurrency, time limit, autoloader and polling
Each pool has its own queue, and you can create as many pools as you want. The configuration chain has four calls:
use Spatie\Async\Pool;
$pool = Pool::create()
// The maximum amount of processes which can run simultaneously.
->concurrency(20)
// The maximum amount of time a process may take to finish in seconds
// (decimal places are supported for more granular timeouts).
->timeout(15)
// Configure which autoloader sub processes should use.
->autoload(__DIR__ . '/../../vendor/autoload.php')
// Configure how long the loop should sleep before re-checking the process statuses in microseconds.
->sleepTime(50000)
;The timeout is in seconds and accepts decimals, while sleepTime is in microseconds, so the two units differ. The pool polls process statuses in a loop rather than waiting on signals, and sleepTime sets how long that loop pauses between checks. The autoload path matters because the children boot fresh and need to know where the autoloader is.
Errors cross the process boundary as catch callbacks or a thrown ParallelError
If an Exception or Error is thrown inside a child, you catch it per process with a catch callback. Without a handler the error is thrown in the parent when you call await() or $pool->wait(). If a child stops unexpectedly without throwing a Throwable, the output written to stderr is wrapped and thrown as Spatie\Async\ParallelError in the parent.
You can type hint the catch callbacks to get one handler per exception type:
$pool
->add(function () {
throw new MyException('test');
})
->catch(function (MyException $e) {
// Handle `MyException`
})
->catch(function (OtherException $e) {
// Handle `OtherException`
});Once an exception is handled, no other handler fires. A later catch for the parent class Exception does not run for a MyException that an earlier handler already took, so order your handlers from most specific to most general.
The event hooks are then, catch and timeout on each ParallelProcess
Adding work to the pool returns a ParallelProcess, and three hooks attach to it. The then callback receives the value your closure or callable returned. The catch callback receives an exception thrown within the process. The timeout callback fires when a process took too long to finish, which ties directly to the timeout setting on the pool.
There is also a functional style. Instead of methods on the pool object, the async and await helper functions let you append with $pool[] = async(function () { ... }) and finish with await($pool). The README example uses usleep with a random_int delay and returns 2 from each of five processes, summing the outputs in then callbacks. Either style gives the same hooks. One practical point is that a result only reaches then through the process output, so what a child returns has to be something the parent can receive, not a live resource.
A stopped pool cannot be reused, and stop only blocks new processes
When one child has completed the task you were after, you can call $pool->stop(). This prevents the pool from starting any additional processes. The README example adds 10000 processes that each return rand(0, 100), and stops the pool in a then callback when one returns 100.
Two limits follow. First, stop prevents new starts; it is described as preventing additional processes, not as a description of killing children already running. Second, a pool is rendered useless after being stopped, so if you need more work done you create a new pool. Do not keep a reference to the stopped pool and keep adding to it. Because the example queues 10000 closures up front, the pool's queue, not the concurrency limit, holds the bulk of that work.
Install with composer, and point at a different PHP binary if needed
The package installs with composer:
composer require spatie/asyncBy default the pool uses php to execute its child processes, found however your environment resolves that name. If the php on the path is not the one you want, withBinary takes a full path:
Pool::create()
->withBinary('/path/to/php');This matters when a server has several PHP versions installed, since the children run under whatever binary you name, and that binary must carry pcntl and posix for true parallelism. The project is MIT licensed and its repository is not archived. The latest releases are 1.8.2 on 2026-02-09, 1.8.1 on 2025-11-28 and 1.8.0 on 2025-08-05, and the last push to the repository was on 2026-10-22.
Editorial conclusion
spatie/async suits PHP scripts and workers that want to fan work out to a bounded number of child processes with a small API, and where pcntl and posix are available. It is a poor fit where those extensions are missing and parallelism is a hard requirement, because the pool quietly runs tasks one after another instead of failing. Before relying on it, check that the runtime you deploy to loads pcntl and posix, and set the autoloader path for the children.
Frequently asked questions
How to install spatie/async?
Install it with composer by running composer require spatie/async. Parallel execution needs the pcntl and posix extensions in your PHP runtime.
How to use spatie/async?
Create a pool with Pool::create(), add closures or Task objects with $pool->add(), attach then, catch and timeout callbacks, and call $pool->wait(). The async and await helper functions are an alternative.
What is synchronous versus async in spatie/async?
The library runs work in parallel child processes through PCNTL. If pcntl and posix are missing, the Pool automatically falls back to synchronous execution of tasks.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/spatie-async)Star history chart for your README
[](https://hysenlabs.com/projects/spatie-async)Was this useful?