Menggali fondasi coroutine di Swoole: Coroutine::create, go, defer, dan cara kerja scheduler, mengapa coroutine menghilangkan callback hell, hook pada operasi I/O, serta praktik menjalankan banyak request HTTP secara concurrent dalam satu worker.

Setelah di episode 6 kita membangun WebSocket dan merasakan kekuatan server realtime — pada episode ini kita menggali mesin yang membuat semua itu mungkin: coroutine. Ini adalah konsep paling khas Swoole dan titik di mana kalian benar-benar berpindah mental dari "developer PHP-FPM" menjadi "developer PHP modern".
Mengapa episode ini penting? Karena coroutine adalah jawaban Swoole atas dilema lama pemrograman async: kode sinkron yang mudah dibaca, dengan performa non-blocking. Setelah episode ini, kalian akan melihat callback-chaining sebagai senjata makan tuan, dan mulai menulis kode concurrency yang bersih.
Bayangkan sebuah kantin dengan satu kasir (worker) dan banyak pembeli (task). Model PHP-FPM seperti memanggil satu kasir per pembeli: mahal, dan setiap kasir diam saja menunggu pembeli selesai makan sebelum melayani orang berikutnya.
Model coroutine seperti satu kasir yang lihai: saat pembeli mengambil lauk (menunggu I/O), kasir pindah melayani pembeli lain, lalu kembali menagih pembeli pertama saat makanannya siap. Satu kasir melayani banyak pembeli bersamaan — tanpa antrean panjang dan tanpa biaya ekstra per pembeli.
Cara membuat coroutine:
use Swoole\Coroutine;
Coroutine::create(function () {
echo "Dalam coroutine\n";
});
echo "Diluar coroutine\n";Dengan shortname aktif, ada fungsi global go(); tetapi karena kita menonaktifkannya (episode 3), kita selalu memakai nama lengkap. Bila dipanggil di dalam context coroutine, bisa juga memakai:
Coroutine::create(function () {
Coroutine::create(function () {
echo "Coroutine bersarang\n";
});
});Coroutine bersarang diperbolehkan, tetapi setiap coroutine baru tetap berjalan dalam satu worker yang sama.
Coroutine berpindah eksekusi di yield point. Contoh paling jelas adalah menunggu I/O:
use Swoole\Coroutine;
Coroutine::create(function () {
sleep(1); // hook ke non-blocking
echo "A selesai\n";
});
Coroutine::create(function () {
sleep(1); // hook ke non-blocking
echo "B selesai\n";
});Tanpa coroutine, dua sleep(1) berurutan butuh 2 detik. Dengan coroutine, keduanya berjalan paralel dalam satu worker dan selesai dalam ~1 detik — karena sleep() di-hook menjadi Swoole\Coroutine\System::sleep() yang menunda coroutine, bukan memblokir worker. Inilah coroutine hooking: fungsi-fungsi blocking standar PHP (sleep, curl, stream, mysqli, dll) secara transparan diubah menjadi non-blocking.
Note
Tidak semua fungsi ter-hook. Fungsi murni CPU (looping, string processing, hashing) tidak bisa "diputar" — ia memblokir worker sampai selesai. Untuk pekerjaan CPU-heavy, gunakan task worker (episode 11). Hook aktif untuk: sleep, curl, stream, PDO/mysqli (via mysqlnd), Redis, file_get_contents, dan client Swoole.
Inilah keajaiban coroutine — bandingkan dua gaya menulis HTTP call:
$client->on('response', function ($resp) use ($client) {
if ($resp->statusCode === 200) {
$client2->on('response', function ($resp2) {
// makin dalam, makin kacau
});
$client2->get('/api/b');
}
});
$client->get('/api/a');use Swoole\Coroutine;
Coroutine::create(function () {
$a = new Coroutine\Http\Client('api-a.local');
$a->get('/endpoint');
$b = new Coroutine\Http\Client('api-b.local');
$b->get('/endpoint');
echo $a->body, $b->body;
});Kode kedua terlihat persis seperti PHP sinkron biasa — tetapi tidak memblokir worker, dan tetap bisa dipakai bersamaan dengan kode lain. Keterbacaan naik drastis, kesalahan logic berkurang.
defer() mendaftarkan fungsi yang dijalankan saat coroutine selesai (normal maupun exception):
use Swoole\Coroutine;
Coroutine::create(function () {
Coroutine::defer(function () {
echo "Bersih-bersih\n";
});
$res = Coroutine\Http\get('https://example.com/api');
echo $res->getBody();
});Analoginya try/finally dalam satu baris. Sempurna untuk melepas lock, menutup file, atau menghapus temporary state — menjamin tidak ada kebocoran resource ketika coroutine mati di tengah jalan.
Beberapa hal yang wajib diketahui sebelum coroutine dipakai produksi:
Coroutine::create. Di server Swoole, setiap event callback (on('Request'), on('Message')) otomatis berjalan dalam context coroutine bila enable_coroutine aktif.stack_size). Puluhan ribu coroutine = puluhan GB virtual memory — diatur via memory_limit dan stack_size.sleep() versi blocking di dalam coroutine akan memblokir seluruh worker.| Masalah | Penyebab | Solusi |
|---|---|---|
| Worker hang | sleep()/file_get_contents tidak ter-hook (disable hook) | Cek swoole build options, atau gunakan Coroutine\System |
| "Coroutine has already been created" | Coroutine::create dari luar context server | Pastikan dipanggil di dalam event callback / coroutine lain |
| Memory naik terus | Stack coroutine membengkak / coroutine bocor | Set stack_size, selidiki coroutine yang tidak pernah selesai |
| Race condition | State bersama diubah banyak coroutine dalam satu worker | Gunakan Channel/Lock (episode 8 & 12) |
Pada episode 7 ini, kalian telah memahami fondasi coroutine di Swoole.
Inti yang harus dibawa pulang:
Coroutine::create() (atau go()) meluncurkan coroutine; defer() menjamin cleanup.Di episode 8 selanjutnya, kita belajar berkomunikasi antar coroutine: Channels & WaitGroup — mengirim data antar coroutine dengan Coroutine\Channel dan menunggu banyak task selesai secara parallel. Sampai jumpa di episode 8!