Async use cases #9
Replies: 19 comments 51 replies
-
|
IMO this is a given (otherwise we wouldn't even be having this discussion), but since it wasn't spelled out, concurrency allows for:
Other languages have introduced concurrency for these reasons and many more, PHP is the only one left behind, and that's all there is to it. |
Beta Was this translation helpful? Give feedback.
-
This is if the async would not be restricted to single thread(worker). See related question about this. |
Beta Was this translation helpful? Give feedback.
-
|
Today I ran into a task where async would be useful. For a stateful server using coroutines, such a delay costs far fewer resources. At the same time, a stateful server gains additional options for preventing this type of attack, because the server knows all its connections and operations and can be optimized accordingly. |
Beta Was this translation helpful? Give feedback.
-
|
As an example, I want to describe a problem that I've been unable to solve for a long time using existing methods within an old Symfony project. We need to calculate delivery costs from multiple carriers (UPS, FedEx, DHL, and many others). Currently, calculations are performed sequentially - first we calculate for one carrier, then the second, and so on, with no parallelism. This creates a huge performance penalty. The calculation code is very large and complex (background HTTP requests, smart packaging logic, distance calculations, specific user restrictions, and many other factors). The core problem is that when we try to parallelize this work, we need to pass serializable data to each worker. As far as I know, most parallel processing tools in PHP serialize data to pass it between workers (at least in cases where the project isn't originally built on Swoole/Amp/ReactPHP, where non-serializable data sharing might be possible - I'm not entirely sure about this). In my project, I cannot properly prepare data for serialization because:
Here's a simplified example: https://github.com/Gemorroj/parallel-nonserializable/blob/master/src/Test.php I have hope that this RFC could help solve problems like mine in projects with classic synchronous architecture. Options like pthreads might theoretically work, but as far as I know, that project is essentially abandoned (partly due to conflicts with fibers). Additionally, it requires ZTS PHP. The ideal solution would be something like pcntl_fork functionality but available outside of CLI mode. |
Beta Was this translation helpful? Give feedback.
-
|
Node.js is single-threaded. DB I/O time is a waste. |
Beta Was this translation helpful? Give feedback.
-
|
Hey @EdmondDantes just read a large chunk of the the last (#\5?) discussion from the RFC. You are a man of passion and some patience 😄 You ask for examples of existing async solutions… two years ago I wrote a “job runner” that gets notifications from PubSub, spins up a Cloud Run job and polls it until completion. Yeah, Cloud Functions can do much the same, but there were, uh, reasons. It’s all IO. The only thing it has to do sync is respond as quickly as possible to PubSub saying “yeah got that”. I did it in Swoole. One hell of a learning curve, but the result has amazed me: <10ms response times, thousands of concurrent jobs monitored off a single instance. And outside spikes it costs close to zero (we’re paying while HTTP requests are active, the polling happens out-of-band save a periodic keep-alive ping back to PubSub, so we’re basically leaching GCP cycles). But, and of some relevance the discussions, this wasn’t some off-the-shelf code I dropped in that magically worked async. Every bit was written with coroutines, atomics, connection pools etc there from the start. And still the early days were segfaults all the way down. Though I’m a huge fan of what Swoole does, the API is minging. And so… I want your RFC to finally succeed: PHP needs it. Keep up the struggle 🥊 |
Beta Was this translation helpful? Give feedback.
-
|
https://www.reddit.com/r/PHP/comments/1ppnwtf/simulating_%D1%81oncurrent_requests_how_we_achieved/ This article explains why developers say PHP needs asynchronous feature |
Beta Was this translation helpful? Give feedback.
-
|
Some real-world examples from projects I've actually worked on:
Both examples above clearly demonstrate situations where requesting resources from third-party APIs may require long I/O waits, and traditional PHP's blocking I/O cannot solve the problem at all - you can only rely on scaling machine resources to improve service concurrency. |
Beta Was this translation helpful? Give feedback.
-
|
This php parallel https://www.php.net/manual/en/book.parallel.php is interesting. Usecase: Eager load many relations on the model in Eloquent. |
Beta Was this translation helpful? Give feedback.
-
|
i have CLI daemon workloads that are long-running and heavily I/O-bound. Today, I am forced to rely on pcntl_fork() and process-based concurrency to work around blocking I/O in PHP. you can take any type of cli daemon as an example: Without native async and non-blocking socket/stream I/O in core, the only viable approach is spawning dozens or hundreds of forked PHP processes. This is inefficient, unnecessarily complex, and the main reason PHP is currently not viable for modern, I/O-heavy CLI daemons and background services. A standardized async and non-blocking I/O foundation in PHP core would allow these workloads to be handled by a single or small number of PHP CLI processes, making PHP a realistic choice for worker daemons, schedulers, and queue consumers—without relying on userland event loops or external extensions. |
Beta Was this translation helpful? Give feedback.
-
|
Dear EdmondDantes, The Current Problem (Without Native Async): Using delayed job queues: Sending the message to a queue (such as Redis Queue, RabbitMQ) and configuring the queue to retry after a certain time. If the wait time needs to increase exponentially after each failure, the configuration can become complex and, in some cases, requires a separate process to manage retry states. How "PHP True Async" Would Solve This Problem: The PHP worker (or main coroutine) would not be blocked: Instead of using sleep(), we could use the suspend() or await() function with a timer. This would allow the coroutine to yield control back to the scheduler while waiting, freeing the worker to process other requests or tasks. Simplified (Conceptual) Code Example: <?php
use function Async\spawn;
use function Async\await;
use function Async\suspend;
use Async\Coroutine;
// Fictional function to simulate sending a webhook and a possible failure
function sendWebhook(string $url, array $payload): void {
echo "[INFO] Attempting to send webhook to: {$url}\n";
// Simulates a failure 50% of the time
if (rand(0, 1) === 0) {
throw new \Exception("Failed to send webhook: Status 503 Service Unavailable");
}
echo "[SUCCESS] Webhook successfully sent to: {$url}\n";
// Here, we would make a real HTTP request
// For example: file_get_contents($url . '?' . http_build_query($payload));
}
// Function that manages sending webhooks with exponential backoff
function sendWebhookWithRetry(string $url, array $payload, int $maxRetries = 5): void {
$retryCount = 0;
$baseDelay = 1; // seconds
while ($retryCount < $maxRetries) {
try {
sendWebhook($url, $payload);
return; // Success, exit the function
} catch (\Exception $e) {
$retryCount++;
echo "[WARN] Webhook failure (attempt {$retryCount}/{$maxRetries}): {$e->getMessage()}\n";
if ($retryCount >= $maxRetries) {
echo "[ERROR] Maximum retry attempts reached for: {$url}\n";
// Here, you can record the final failure in a persistent log
// or trigger a critical alert.
return;
}
// Exponential backoff calculation: baseDelay * 2^(retryCount - 1)
// E.g.: 1s, 2s, 4s, 8s, 16s
$delay = $baseDelay * pow(2, $retryCount - 1);
echo "[INFO] Waiting {$delay} seconds before the next attempt...\n";
// Instead of sleep(), we use suspend() to yield to the scheduler
// An awaitable timer could be created by a utility function
// that returns a Completable which completes after a set duration.
// For this example, we’ll simulate with a suspension loop.
$startTime = microtime(true);
while (microtime(true) - $startTime < $delay) {
suspend(); // Yield control to other coroutines
}
}
}
}
// Simulation of a main script that triggers multiple webhooks
$webhookWithRetryTasks = new Async\TaskGroup(captureResults: true);
spawn with $webhookWithRetryTasks sendWebhookWithRetry("https://erp.example.com/webhook", ["data" => "job_completed_1"]);
spawn with $webhookWithRetryTasks sendWebhookWithRetry("https://payments.example.com/webhook", ["data" => "payment_confirmed_2"]);
spawn with $webhookWithRetryTasks sendWebhookWithRetry("https://notifications.example.com/webhook", ["data" => "user_registered_3"]);
try {
[$erp, $payments, $notifications] = await $webhookWithRetryTasks;
return [
'erp' => $erp,
'payments' => $payments,
'notifications' => $notifications,
];
} catch (\Exception $e) {
logError("Webhook loading failed", $e);
return ['error' => $e->getMessage()];
} |
Beta Was this translation helpful? Give feedback.
-
|
Websocket. WebSocket support makes no sense without asynchrony. This is the main reason why PHP doesn’t work with WebSockets. |
Beta Was this translation helpful? Give feedback.
-
|
One real-world scenario that hasn’t been discussed much yet is early cancellation on client disconnect. Today, if a client closes the connection, PHP typically continues executing the request: outbound HTTP calls keep running, database queries complete, and timers expire — even though the result will never be delivered. This leads to unnecessary resource consumption and wasted I/O. With coroutine-based async execution and cancellation, this changes fundamentally. Once the client disconnects, the coroutine can be cancelled, and all pending I/O operations can be aborted cooperatively, immediately releasing resources. This is especially relevant in real-world scenarios like mobile networks, users navigating quickly between pages, and dashboards or streaming endpoints where clients frequently connect and disconnect. To me, this is a strong example of how cancellation is not a “nice to have”, but a core requirement for PHP as a network-oriented language — and it fits naturally into the model described by this RFC. |
Beta Was this translation helpful? Give feedback.
-
|
I’ve been following this RFC. Thank you for the monumental effort. I want to share a real-world example of why this RFC is crucial not just for high-load scenarios, but for general-purpose library development. I am writing a Telegram client (MTProto) in PHP — ProtoBrick: https://github.com/Proto-Brick/php-mtproto-client/ MTProto is a complex stateful protocol: it requires a persistent TCP connection, encryption, and RPC calls. I am currently using Amphp v3. Implementing such a client fully in standard PHP is problematic. At any given moment, the library must maintain at least five independent processes: a persistent Read Loop, session keep-alive ping timers, hundreds of individual timeout timers for RPC requests, background state persistence to disk, and update queue processing loops. But even with current frameworks, we remain hostages to two problems that True Async would solve once and for all: 1. The "Fragile" Event Loop ProblemEven if I write perfect async code internally, the end user will break it. Classically: a developer receives a message in a bot, wants to pause, and writes In the current reality (Amp/React), this stops the entire process. My library stops sending ping packets and handling keep-alives; Telegram detects a timeout and drops the connection. The user experiences a script crash and complains about a "buggy lib." Currently, to protect against this, we have to use workarounds:
With True Async, that ill-fated 2. State Management Hell (RPC) and "Function Coloring"Implementing the RPC pattern ("sent request -> waiting for response with specific ID") currently requires manual micromanagement of timers and promises. Furthermore, due to "function coloring," I am forced to duplicate all library methods: for every one of the 1000+ Telegram API methods, I have to generate a A. Current Async (Amp/React) The logic is smeared: we need to manually cancel timers and clean the registry in every branch (both success and error), otherwise, we risk memory leaks or attempts to resolve a promise twice. Pseudocode approximating reality: class RpcClient {
private array $pending = []; // [msg_id => ['def' => DeferredFuture, 'timer' => string]]
/** @return Future<RpcResponse> */
public function call(RpcRequest $req, float $timeout = 30): Future {
$deferred = new DeferredFuture();
$msgId = $req->getMsgId();
// Manual creation of timeout timer
$timerId = EventLoop::delay($timeout, function () use ($msgId) {
if (isset($this->pending[$msgId])) {
$this->pending[$msgId]['def']->error(new RuntimeException("Timeout"));
unset($this->pending[$msgId]);
}
});
$this->pending[$msgId] = ['def' => $deferred, 'timer' => $timerId];
$this->transport->write($req->serialize());
return $deferred->getFuture(); // The function gets "colored" (returns Future)
}
private function readLoop(): void {
// Launched as a background task via Amp\async()
while ($packet = $this->transport->read()) {
$id = $packet->getMsgId();
if ($entry = $this->pending[$id] ?? null) {
EventLoop::cancel($entry['timer']); // Mandatory manual cleanup
unset($this->pending[$id]);
if ($packet->isError()) {
$entry['def']->error(new RpcErrorException(...));
} else {
$entry['def']->complete($packet->getBody());
}
}
}
}
}
// User call, uses call() internally
$client->sendMessage(...);
$client->sendMessageAsync(...)->await();B. True Async (RFC 1.7) The logic gets rid of wrappers and manual state management. Micromanagement disappears: class RpcClient {
private array $awaiting = []; // [msg_id => Completable]
// Method returns result immediately or throws exception
public function call(RpcRequest $req, int $timeoutSec = 30): RpcResponse {
$msgId = $req->getMsgId();
$res = new ResponseCompletable(); // Implements Completable
$this->awaiting[$msgId] = $res;
try {
$this->transport->write($req->serialize());
// Wait for response OR AwaitCancelledException
return Async\await($res, Async\timeout($timeoutSec * 1000));
} finally {
unset($this->awaiting[$msgId]); // Centralized guaranteed cleanup
}
}
private function readLoop(): void {
Async\spawn(function() {
while ($packet = $this->transport->read()) {
$id = $packet->getMsgId();
$completable = $this->awaiting[$id] ?? null;
if ($packet->isError()) {
$completable?->fail(new RpcErrorException(...));
} else {
$completable?->complete($packet->getBody());
}
}
});
}
}
$client->sendMessage();
// or in background
Async\spawn(fn() => $client->sendMessage(...));I would also like to separately mention how I plan to use other True Async mechanisms: Transitioning to TrueAsync and its Cancellable by design concept brings the risk of coroutine interruption at any suspension point. For MTProto, this is problematic: interrupting Sequence Number updates or session disk writes can violate protocol state machine logic and lead to desynchronization with the server. Although the client is capable of self-recovery, I plan to use Async\protect as the main tool to guard these bottlenecks. This guarantees their integrity, while all other network I/O remains cancellable by default, allowing the client to be maximally responsive to system signals or timeouts. Furthermore, I plan to implement Structured Concurrency via the Async\Scope mechanism. In the MTProto client, this solves the problem of "abandoned" coroutines: background tasks like readLoop, pingLoop, and periodic session saving will be rigidly tied to the scope hierarchy. When stopping the client or closing a specific connection, I won't have to manually clean up timers and queues—it will be sufficient to cancel the corresponding Scope, and all child processes will terminate automatically and predictably. I really hope this RFC passes! |
Beta Was this translation helpful? Give feedback.
-
|
back to years ago, I worked on an ecommerce project which allows customers to add their own images to the product. |
Beta Was this translation helpful? Give feedback.
-
|
+1 case for wp |
Beta Was this translation helpful? Give feedback.
-
|
While working on this segregation of relation definitions into closures, I realized a case when true async would mess things up. The BelongsTo relation family needs a param of relation name. When not provided it is guessed through debug backtrace. I set a private static property to hold that name so I can read it when guessing and bypass debug backtrace. If a relation executes an exists query inside its definition and the async worker switches scopes (while that query is executed), handling the next relation which executes another query, the static flag could be messed up. |
Beta Was this translation helpful? Give feedback.
-
|
No expert on I’m also only vaguely conversant with Laravel, but for some time have been meaning to check out how Octane handles this problem. What do they do? |
Beta Was this translation helpful? Give feedback.
-
|
Nativephp is a project that ports php to Desktop/Mobile applications alongside with laravel ecossystem. It has everything that a standard web application would have. In a case where php would be the renderer is crucial to have true async to not slow down the UI when performing a IO bound op. And native applications have a lot of IO bound operations like reading the db, sending notifications, grabbing os preferences, bluetooth, wifi, performing requests, battery. Plus true async as it is, channels could work a lot a standard way to read OS state related stuff. Plus in the future it could grow to a standard way to perform modern multithreading with php, with some kind of standard thread pools like we already have in kotlin. I m so happy that this feature have a big chance to come to standard php. Can't wait to use in prod |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
In this thread you may share examples of tasks you have already solved using async in PHP, or tasks that would be easier to solve with it.
You may include code examples if you wish.
This topic may become useful for shaping the API.
You will also help other PHP developers learn something new or look at old problems from a different perspective.
That’s why this thread can be extremely useful for everyone!
Beta Was this translation helpful? Give feedback.
All reactions