惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LINUX DO - 最新话题
Microsoft Azure Blog
Microsoft Azure Blog
The Register - Security
The Register - Security
Vercel News
Vercel News
Cloudbric
Cloudbric
Recent Announcements
Recent Announcements
O
OpenAI News
Y
Y Combinator Blog
Hacker News: Ask HN
Hacker News: Ask HN
I
InfoQ
WordPress大学
WordPress大学
S
Secure Thoughts
P
Proofpoint News Feed
Hacker News - Newest:
Hacker News - Newest: "LLM"
小众软件
小众软件
I
Intezer
博客园 - 司徒正美
Google DeepMind News
Google DeepMind News
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
G
GRAHAM CLULEY
P
Palo Alto Networks Blog
Attack and Defense Labs
Attack and Defense Labs
MyScale Blog
MyScale Blog
P
Proofpoint News Feed
美团技术团队
V
Vulnerabilities – Threatpost
Scott Helme
Scott Helme
C
Check Point Blog
H
Help Net Security
博客园 - Franky
AWS News Blog
AWS News Blog
MongoDB | Blog
MongoDB | Blog
Project Zero
Project Zero
N
Netflix TechBlog - Medium
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
V2EX - 技术
V2EX - 技术
Cisco Talos Blog
Cisco Talos Blog
Latest news
Latest news
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Last Week in AI
Last Week in AI
V
V2EX
C
Cybersecurity and Infrastructure Security Agency CISA
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
大猫的无限游戏
大猫的无限游戏
N
News and Events Feed by Topic
Hugging Face - Blog
Hugging Face - Blog
The Hacker News
The Hacker News
Schneier on Security
Schneier on Security
PCI Perspectives
PCI Perspectives
Apple Machine Learning Research
Apple Machine Learning Research

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
PHP Fibers in Production: 4 Real Cases Where They Beat curl_multi and Queues
Gabriel Anha · 2026-05-24 · via DEV Community

Fibers shipped in PHP 8.1 in November 2021. The RFC was small, the API has four methods, and most Laravel codebases still don't touch them. Open vendor/ in a fresh Laravel project and you'll find Guzzle, Symfony HttpClient, and ReactPHP all using them under the hood. Your own code? Probably none.

That's fine for a CRUD app. The moment you fan out to five HTTP services, stream a multi-gigabyte file, or wire a webhook dispatcher, the queue-or-block dichotomy gets expensive. Fibers fix a specific class of problem that queues over-solve and curl_multi under-solves. Here are four cases where they earn their keep.

What Fibers actually are (60-second mental model)

A Fiber is a coroutine. Not a thread. Not a process. One stack of execution that can pause itself and resume later, all inside the same PHP process.

The whole API:

$fiber = new Fiber(function (): void {
    echo "step 1\n";
    $resumed = Fiber::suspend("paused");  // hands control back
    echo "step 2 with: $resumed\n";
});

$out = $fiber->start();           // "step 1", returns "paused"
$fiber->resume("hello");          // "step 2 with: hello"
$fiber->isTerminated();           // true

Enter fullscreen mode Exit fullscreen mode

Fiber::suspend() is the magic. It can only be called from inside a running Fiber. Call it from the main flow and you get Fiber\FiberError: Cannot call Fiber::suspend() outside of a Fiber. The caller of start() or resume() gets back whatever you suspended with.

Two things to internalise before we go further:

  1. Fibers are cooperative. Nothing preempts them. If a Fiber doesn't call suspend(), it runs to completion and blocks the main flow.
  2. They share the same process. No GIL, no memory copy, no IPC. Globals are shared. So is the PDO connection, which is the first gotcha most people hit (more below).

Case 1: Parallel HTTP fan-out

Your checkout endpoint calls the payment gateway, the fraud service, the loyalty API, the shipping calculator, and the warehouse availability service. Five calls, ~400ms each, sequential. That's 2 seconds on the happy path.

curl_multi_exec() solves this but the API is a 20-year-old polling loop with do {} while and select-style fd watching. It works. Reading it does not bring joy.

Here's the Fiber version. The pattern: each HTTP call lives in its own Fiber. A small scheduler pumps curl_multi and resumes Fibers as their handles finish.

final class ParallelHttp
{
    /** @param array<string, string> $urls keyed by label */
    public static function get(array $urls): array
    {
        $mh = curl_multi_init();
        $handles = [];
        $fibers = [];

        foreach ($urls as $key => $url) {
            $fibers[$key] = new Fiber(function () use ($url, $mh, &$handles, $key) {
                $ch = curl_init($url);
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
                curl_setopt($ch, CURLOPT_TIMEOUT, 5);
                curl_multi_add_handle($mh, $ch);
                $handles[$key] = $ch;

                // hand control back to the scheduler until curl says done
                Fiber::suspend();

                $body = curl_multi_getcontent($ch);
                curl_multi_remove_handle($mh, $ch);
                curl_close($ch);
                return $body;
            });
            $fibers[$key]->start();
        }

        // pump until every handle is finished
        do {
            curl_multi_exec($mh, $active);
            curl_multi_select($mh, 0.05);
        } while ($active > 0);

        curl_multi_close($mh);

        $results = [];
        foreach ($fibers as $key => $fiber) {
            $results[$key] = $fiber->resume();
        }
        return $results;
    }
}

$out = ParallelHttp::get([
    'payment'  => 'https://api.example.com/payment/check',
    'fraud'    => 'https://api.example.com/fraud/score',
    'loyalty'  => 'https://api.example.com/loyalty/balance',
    'shipping' => 'https://api.example.com/shipping/quote',
    'stock'    => 'https://api.example.com/warehouse/stock',
]);

Enter fullscreen mode Exit fullscreen mode

Numbers from a synthetic benchmark against five endpoints with an artificial 400ms server-side delay: sequential cURL takes ~2,050ms wall-clock; the Fiber + curl_multi version finishes in ~440ms. That's the math you'd expect. Wall-clock collapses to roughly the slowest call plus a small scheduling overhead. The Guzzle async docs report similar shapes and ReactPHP's HTTP client benchmarks sit in the same range.

The Fiber version isn't faster than raw curl_multi. It's the same network primitive underneath. What changes is that each Fiber gets a normal-looking function body. No callbacks, no then(), no promise chains. That matters when the failure handling gets non-trivial. Try/catch works exactly the way you'd expect.

Case 2: Stream processing with backpressure

Reading a 10GB CSV with fopen + fgetcsv works fine until you batch-write to a database that can't keep up. The naive script reads as fast as PHP can parse and the downstream consumer either OOMs the queue or drops rows.

Fibers let the producer pause when the consumer is behind. Here's a pattern with a bounded buffer:

final class BoundedChannel
{
    /** @var list<array<string, string>> */
    private array $buffer = [];
    private ?Fiber $waitingProducer = null;
    private ?Fiber $waitingConsumer = null;
    private bool $closed = false;

    public function __construct(private readonly int $capacity = 100) {}

    public function send(array $row): void
    {
        while (count($this->buffer) >= $this->capacity) {
            $this->waitingProducer = Fiber::getCurrent();
            Fiber::suspend(); // wait for the consumer to drain
        }
        $this->buffer[] = $row;
        if ($this->waitingConsumer !== null) {
            $c = $this->waitingConsumer;
            $this->waitingConsumer = null;
            $c->resume();
        }
    }

    public function receive(): ?array
    {
        while ($this->buffer === [] && !$this->closed) {
            $this->waitingConsumer = Fiber::getCurrent();
            Fiber::suspend();
        }
        if ($this->buffer === []) return null;
        $row = array_shift($this->buffer);
        if ($this->waitingProducer !== null) {
            $p = $this->waitingProducer;
            $this->waitingProducer = null;
            $p->resume();
        }
        return $row;
    }

    public function close(): void { $this->closed = true; }
}

Enter fullscreen mode Exit fullscreen mode

A real ingestion pipeline wires a producer Fiber and a consumer Fiber against the channel:

$channel = new BoundedChannel(capacity: 500);

$producer = new Fiber(function () use ($channel): void {
    $fp = fopen('orders-2026.csv', 'r');
    $header = fgetcsv($fp);
    while (($row = fgetcsv($fp)) !== false) {
        $channel->send(array_combine($header, $row));
    }
    fclose($fp);
    $channel->close();
});

$consumer = new Fiber(function () use ($channel): void {
    $batch = [];
    while (($row = $channel->receive()) !== null) {
        $batch[] = $row;
        if (count($batch) === 50) {
            OrderImporter::insertBatch($batch); // blocking DB write
            $batch = [];
        }
    }
    if ($batch !== []) OrderImporter::insertBatch($batch);
});

$producer->start();
$consumer->start();

Enter fullscreen mode Exit fullscreen mode

The producer parks itself once the buffer hits 500 rows. The consumer drains, the producer resumes. Memory stays flat. A 9GB CSV with this pattern keeps resident memory under 100MB across the run, because the buffer caps at 500 rows. The interesting comparison isn't speed (the DB is the bottleneck), it's that the script can't OOM regardless of how fast fgetcsv reads.

A generator gives you laziness, but generators can't yield from inside helper methods without polluting the entire call chain. Fibers can. That's the actual ergonomic win.

Case 3: Replacing generator coroutines

Before Fibers, people did this in PHP for async work:

function fetchUser(int $id): Generator {
    $raw = yield $http->get("/users/$id");        // a Promise
    $orders = yield $http->get("/orders?u=$id");
    return ['user' => $raw, 'orders' => $orders];
}

Enter fullscreen mode Exit fullscreen mode

You needed a "coroutine runner" that walked the generator, awaited each yielded promise, and pushed the result back in. The pattern works (Recoil, Amp v2 used it heavily, even Laravel Octane had flavors of it) but the signature : Generator was always wrong. The function returns array. The type system never agreed with reality.

Fibers fix this. Same logic, real return types:

public function fetchUser(int $id): UserBundle
{
    $user = $this->client->get("/users/$id");      // suspends internally
    $orders = $this->client->get("/orders?u=$id");
    return new UserBundle($user, $orders);
}

Enter fullscreen mode Exit fullscreen mode

The client suspends the current Fiber while waiting on the socket. The function reads as straight-line synchronous code. PHPStan, Psalm, IDE autocomplete: all of them get the right types. The Amp team rewrote everything around Fibers in v3 (release notes) for exactly this reason. ReactPHP shipped react/async 4.x doing the same: await() is a fiber-based primitive that lets a promise-returning library look synchronous.

If you've got a codebase with Generator-based "async" helpers from 2019, ripping them out for Fibers usually cuts 30-40% of the file in pure ceremony. No behaviour change.

Case 4: Inside ReactPHP / AMPHP without converting the whole codebase

You don't have to go all-in on an event loop to use Fibers. This is the point most "should I use Amp?" posts miss.

A Symfony Messenger handler that needs to fan out to three webhooks can pull in a Fiber-aware HTTP client locally, run a small await inside the handler, and stay otherwise vanilla:

use Amp\Http\Client\HttpClientBuilder;
use Amp\Http\Client\Request;
use function Amp\async;
use function Amp\Future\awaitAll;

final class DispatchWebhooksHandler
{
    public function __invoke(OrderPlaced $event): void
    {
        $client = HttpClientBuilder::buildDefault();
        $urls = WebhookSubscriptions::for($event->orderId);

        $futures = array_map(
            fn(string $url) => async(fn() =>
                $client->request(new Request($url, 'POST', json_encode($event)))
            ),
            $urls,
        );

        [$errors, $responses] = awaitAll($futures);
        foreach ($errors as $url => $err) {
            FailedWebhook::record($url, $err->getMessage());
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

async() spawns a Fiber. awaitAll() blocks the handler until every Fiber finishes, but only this handler. The rest of your Symfony app keeps doing whatever it's doing. No event loop running globally. No "convert the whole framework to async."

The trick is that Amp's EventLoop initialises lazily on first use and shuts down cleanly when there's nothing pending. Same with react/async. You get coroutine ergonomics in one method without infecting the rest of the codebase.

Where Fibers don't help

PDO is blocking. Sticking a PDO::query() inside a Fiber doesn't make it async. The Fiber blocks the entire process until libpq returns. Same for mysqli in default mode. Same for file_get_contents() on a local file. Same for sleep().

CPU-bound work (image resizing, JSON encoding a 200MB structure, bcrypt) gets nothing from Fibers. They're for I/O wait. If the bottleneck is a tight for loop hashing things, you need pthreads, parallel, or pcntl_fork. Not coroutines.

The mental check: would this operation, in Node.js, return a Promise from a libuv-backed call? If yes, a Fiber-based wrapper will help. If no (your bottleneck is PHP itself), Fibers won't.

The big asterisk: there are async PDO replacements (amphp/postgres, amphp/mysql) that work cooperatively with Fibers. Worth knowing they exist. Worth knowing that swapping them in is rarely free. Eloquent and Doctrine both assume blocking drivers.

The error-handling gotcha

Exceptions inside Fibers behave almost like you'd expect. Almost.

$fiber = new Fiber(function (): void {
    Fiber::suspend();
    throw new RuntimeException("boom");
});

$fiber->start();
try {
    $fiber->resume(); // exception propagates HERE, not where it was thrown
} catch (RuntimeException $e) {
    // caught
}

Enter fullscreen mode Exit fullscreen mode

The exception travels to whoever called resume(). That's usually fine. The trap is Fiber::throw():

$fiber = new Fiber(function (): void {
    try {
        Fiber::suspend();
    } catch (DomainException $e) {
        // the throw() call lands inside the suspend
    }
});
$fiber->start();
$fiber->throw(new DomainException("cancelled"));

Enter fullscreen mode Exit fullscreen mode

throw() injects an exception at the suspend() point. If the Fiber doesn't catch it, the exception bubbles up to the caller of throw(). People use this for cancellation, but it's easy to set up an exception that crosses three Fibers and lose track of who actually catches it.

Rule of thumb: keep Fiber bodies short. If a Fiber needs error handling more sophisticated than try/catch around its entry point, you probably want to be returning a Result-style object across suspend() instead of throwing through it.

What this means for your codebase

Fibers are a primitive, not a feature you bolt on. The right places to reach for them: parallel I/O fan-out, backpressure-aware streaming, replacing generator-based async helpers, and embedded use inside otherwise-blocking frameworks. The wrong places: CPU work, PDO, anywhere you don't have a clear suspend-point story.

If you already ship a Laravel or Symfony app, the lowest-risk first step is Case 4: a single handler that spawns a few Fibers via Amp or react/async, runs locally, and exits. No global event loop. No infrastructure change. Measure the win, then expand.

What's the slowest fan-out in your current app, and would Case 4 fit it without a framework rewrite?


If this was useful

Fibers are the runtime primitive. The bigger question is architectural: should this fan-out live in your domain layer or in an adapter? That's where most codebases get tangled. Decoupled PHP is the architectural layer your codebase reaches for once the framework defaults stop being enough; it covers how to keep async I/O on the edges and your domain code blissfully synchronous.

Decoupled PHP — Clean and Hexagonal Architecture for Applications That Outlive the Framework

Available on Kindle, Paperback, and Hardcover. English, German, and Japanese editions out now — Portuguese and Spanish coming soon.