High-Performance Servers with Swoole
Serve thousands of connections with coroutine-based Swoole.
High-Performance Servers with Swoole is a free PHP Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the PHP Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
What Swoole Is
Swoole (and its fork OpenSwoole) is a C extension that turns PHP into a high-performance, coroutine-based, event-driven runtime. Instead of PHP-FPM spawning a process per request, a Swoole server boots your app once and keeps it resident in memory, serving requests from a pool of long-lived workers with built-in coroutines. The result: order-of-magnitude throughput gains and native concurrency inside a single request.
The Persistent HTTP Server
The classic example: a full HTTP server in a script. It listens, dispatches each request to a worker, and never re-bootstraps the framework. Note this requires ext-swoole — it does not run on stock PHP CLI.
<?php
$server = new Swoole\Http\Server('0.0.0.0', 9501);
$server->set(['worker_num' => 4]);
$server->on('request', function ($request, $response) {
$response->header('Content-Type', 'application/json');
$response->end(json_encode(['hello' => $request->server['request_uri']]));
});
echo "listening on :9501\n";
$server->start();Coroutines, Not Callbacks
Swoole's killer feature is coroutines: write blocking-style sequential code that the runtime schedules cooperatively. Coroutine\run() opens a coroutine context; go() launches a new coroutine. Each coroutine has its own stack, like a Fiber, but Swoole schedules them automatically at I/O points.
<?php
use Swoole\Coroutine as Co;
Co\run(function () {
go(function () { Co::sleep(1); echo "A done\n"; });
go(function () { Co::sleep(1); echo "B done\n"; });
// Both sleep concurrently -> total ~1s, not 2s
});Hooked Blocking Functions
The magic that makes coroutines ergonomic: runtime hooks. Swoole transparently rewrites blocking calls — PDO, Redis, curl, sleep, file_get_contents — into non-blocking coroutine-aware versions. Your ordinary $pdo->query() yields the coroutine instead of freezing the worker.
<?php
use Swoole\Runtime;
use Swoole\Coroutine as Co;
Runtime::enableCoroutine(SWOOLE_HOOK_ALL); // hook PDO, curl, sleep, etc.
Co\run(function () {
go(function () { sleep(1); echo "task 1\n"; }); // sleep() is now async!
go(function () { sleep(1); echo "task 2\n"; });
}); // finishes in ~1s because sleep was hookedConcurrent I/O with WaitGroup
To fan out work and join, Swoole offers a WaitGroup (like Go's sync.WaitGroup). Launch coroutines, add() per task, done() on completion, and wait() to block until all finish — concurrently.
<?php
use Swoole\Coroutine as Co;
use Swoole\Coroutine\WaitGroup;
Co\run(function () {
$wg = new WaitGroup();
$results = [];
foreach (['eu', 'us', 'asia'] as $region) {
$wg->add();
go(function () use ($wg, $region, &$results) {
Co::sleep(0.5); // simulated API call
$results[$region] = "ok-$region";
$wg->done();
});
}
$wg->wait(); // all three ran in parallel (~0.5s)
var_dump($results);
});Channels for Communication
Coroutines coordinate via Channels — typed, bounded queues that suspend the producer when full and the consumer when empty. This is CSP-style messaging, avoiding shared-state locks entirely.
<?php
use Swoole\Coroutine as Co;
use Swoole\Coroutine\Channel;
Co\run(function () {
$chan = new Channel(2);
go(function () use ($chan) {
foreach (range(1, 3) as $n) $chan->push($n);
$chan->push(null); // sentinel
});
go(function () use ($chan) {
while (($v = $chan->pop()) !== null) echo "got $v\n";
});
});Connection Pooling
A persistent server should not open a DB connection per request. Swoole provides coroutine-safe connection pools so workers reuse a fixed set of connections across thousands of concurrent coroutines, eliminating connection churn.
<?php
use Swoole\Database\PDOConfig;
use Swoole\Database\PDOPool;
use Swoole\Coroutine as Co;
Co\run(function () {
$pool = new PDOPool((new PDOConfig())
->withHost('127.0.0.1')->withDbname('app')
->withUsername('u')->withPassword('p'), 8);
go(function () use ($pool) {
$pdo = $pool->get(); // borrow
$row = $pdo->query('SELECT NOW()')->fetch();
$pool->put($pdo); // return to pool
var_dump($row);
});
});State Leaks: The Persistent-Memory Trap
Because the process lives across requests, global and static state persists. A static cache that grows unbounded, a singleton holding request-specific data, or a leaked DB transaction will corrupt later requests and exhaust memory. Rules: avoid request-scoped data in statics/singletons, reset per-request context, and set max_request so workers recycle periodically.
<?php
$server = new Swoole\Http\Server('0.0.0.0', 9501);
$server->set([
'worker_num' => 8,
'max_request' => 10000, // recycle a worker after N requests to bound leaks
]);
// Never store $request data in a static property between requests!Coroutine-Local Context
Since one worker interleaves many coroutines, you cannot use plain statics for per-request data. Swoole gives each coroutine a context (Coroutine::getContext()) — an object whose lifetime is the coroutine, perfect for the current user, request ID, or transaction handle.
<?php
use Swoole\Coroutine as Co;
Co\run(function () {
go(function () {
$ctx = Co::getContext();
$ctx['request_id'] = 'req-123';
// ... deep call chain can read Co::getContext()['request_id']
echo Co::getContext()['request_id'], PHP_EOL;
});
});Process Model and Task Workers
Swoole runs a master process (manages event loop threads), a manager (spawns/monitors workers), worker processes (run your request handlers + coroutines), and optional task workers for heavy, blocking, or CPU-bound jobs offloaded via $server->task(). Use task workers for image processing or long CPU work so they do not stall request coroutines — giving you both concurrency and parallelism.
Defer and Timers
Swoole lets you schedule work after the current coroutine finishes with Coroutine::defer() — ideal for releasing pooled resources reliably even if an exception is thrown. Server-wide timers (Timer::tick) run periodic jobs like cache refresh inside a worker without an external cron.
<?php
use Swoole\Coroutine as Co;
Co\run(function () {
go(function () {
$conn = 'borrowed-connection';
Co::defer(function () use ($conn) {
echo "released $conn\n"; // runs when coroutine exits, even on error
});
echo "using $conn\n";
});
});Quick Check
Why must you avoid storing request data in static properties in a Swoole server?
Recap
Swoole turns PHP into a resident, coroutine-driven server:
- The app boots once; a worker pool serves requests with built-in coroutines (
Co\run/go()). - Runtime hooks make ordinary PDO/curl/sleep non-blocking; WaitGroup and Channels coordinate concurrent work.
- Connection pools reuse DB connections across coroutines.
- Persistent memory means you must avoid state leaks: use
Coroutine::getContext()for per-request data, setmax_request, and offload heavy work to task workers.
Frequently asked questions
Is the “High-Performance Servers with Swoole” lesson free?
Yes — the full text of “High-Performance Servers with Swoole” is free to read here on the web, and the PHP Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the PHP Academy course, upgrade to CoddyKit PRO.
What will I learn in “High-Performance Servers with Swoole”?
Serve thousands of connections with coroutine-based Swoole. You practise PHP Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start PHP Academy?
No prior experience is required. PHP Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “High-Performance Servers with Swoole” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this PHP Academy lesson?
Yes. Every PHP Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- The PHP Concurrency Model
- PHP 8.1 Fibers
- Event Loops with ReactPHP
- High-Performance Servers with Swoole