Mengeluarkan pekerjaan CPU-heavy dari worker utama dengan task worker: memakai task dan on Task untuk image processing, PDF generation, dan pengiriman email async, memahami task_worker_num, finish callback, dan task_enable_coroutine.

Setelah di episode 10 kita mengelola waktu dan proses — pada episode kali ini kita menangani masalah yang sering mengejutkan developer Swoole: pekerjaan CPU-heavy. Coroutine membuat I/O tidak memblokir worker, tetapi komputasi murni (loop panjang, kompresi, resize gambar, generate PDF) tetap memblokir — karena CPU sedang sibuk dan tidak ada event yang bisa "diputar".
Mengapa episode ini penting? Satu request yang melakukan resize gambar 5 detik akan membuat seluruh worker itu beku selama 5 detik — ratusan request lain ikut terlambat. Task worker adalah solusi arsitektural Swoole untuk masalah ini, dan wajib dikuasai sebelum aplikasi kalian masuk produksi.
Bandingkan dua jenis pekerjaan:
| Jenis | Perilaku di worker | Kapan selesai |
|---|---|---|
| I/O (DB, HTTP) | Coroutine menunda, worker lanjut tugas lain | Saat data tiba |
| CPU (resize, PDF, hash) | Worker macet total sampai selesai | Hanya setelah komputasi rampung |
Kode berikut membuat worker macet selama komputasi hash:
$server->on('Request', function ($req, $res) {
// Loop hash 3 detik — worker membeku, request lain menunggu!
$hash = '';
for ($i = 0; $i < 3000000; $i++) {
$hash = hash('sha256', $hash . $i);
}
$res->end($hash);
});Task worker adalah pool proses terpisah yang khusus mengeksekusi tugas berat. Worker utama hanya mengirimkan tugas (instan, tidak memblokir) dan melanjutkan melayani request lain.
$server = new Server('0.0.0.0', 9501);
$server->set([
'worker_num' => 4, // worker utama
'task_worker_num' => 4, // task worker
]);Alur lengkapnya:
$server->task($data) — data dikirim ke task worker.on('Task') dan mengembalikan hasil (return).on('Finish').<?php
use Swoole\Server;
$server = new Server('0.0.0.0', 9501);
$server->set(['worker_num' => 4, 'task_worker_num' => 4]);
$server->on('Receive', function (Server $server, $fd, $reactorId, $data) {
// Kirim tugas ke task worker, hasil di on('Finish')
$server->task(['job' => 'resize', 'path' => $data]);
$server->send($fd, "Tugas diterima, diproses di background\n");
});
$server->on('Task', function (Server $server, $taskId, $workerId, $data) {
// Ini berjalan di TASK WORKER — aman untuk CPU-heavy
$image = new Imagick($data['path']);
$image->resizeImage(800, 800, Imagick::FILTER_LANCZOS, 1);
$image->writeImage($data['path'] . '.webp');
return 'resized'; // hasil dikembalikan
});
$server->on('Finish', function (Server $server, $taskId, $data) {
echo "Task $taskId selesai: $data\n";
});
$server->start();Perhatikan: on('Task') berjalan di proses task worker — jadi variabel state worker biasa tidak tersedia di sana. Data harus di-pass lengkap lewat parameter $data.
Tidak semua tugas butuh hasil — kirim email, update cache, tulis log. Untuk pola fire-and-forget, cukup panggil task() dan abaikan on('Finish'):
$server->on('Request', function ($req, $res) use ($server) {
$emailData = ['to' => 'user@example.com', 'body' => $req->post['message'] ?? ''];
$server->task($emailData); // instan, tidak menunggu
$res->end('Email sedang dikirim');
});
$server->on('Task', function (Server $server, $taskId, $workerId, $data) {
send_email_smtp($data['to'], $data['body']); // lambat tapi di task worker
return true;
});Request selesai dalam milidetik; pengiriman email yang butuh detik tidak lagi menghalangi user.
Secara default task worker tidak punya coroutine context. Aktifkan task_enable_coroutine untuk memakai coroutine di dalam task — memungkinkan satu task worker menangani banyak task I/O-heavy secara parallel:
$server->set([
'task_worker_num' => 4,
'task_enable_coroutine' => true,
]);
$server->on('Task', function (Server $server, Server\Task $task) {
// $task->data — payload task
// Di sini boleh pakai coroutine
$resp = Coroutine\Http\get('https://api.render.example/v1/pdf');
$task->finish($resp->getBody());
});Dengan mode ini, bentuk callback berubah: parameter kedua adalah object Swoole\Server\Task, dan hasil dikirim via $task->finish().
Contoh produksi gabungan — upload gambar lalu resize di task worker:
<?php
use Swoole\Server;
$server = new Server('0.0.0.0', 9501);
$server->set([
'worker_num' => 4,
'task_worker_num' => 8, // 8 task worker untuk resize
]);
$server->on('Receive', function (Server $server, $fd, $reactorId, $data) {
$payload = json_decode($data, true);
$server->task([
'job' => 'resize',
'file' => $payload['file'],
'sizes' => [100, 300, 800],
]);
$server->send($fd, 'OK');
});
$server->on('Task', function (Server $server, $taskId, $workerId, $data) {
if ($data['job'] !== 'resize') {
return;
}
foreach ($data['sizes'] as $size) {
$img = new Imagick($data['file']);
$img->resizeImage($size, $size, Imagick::FILTER_LANCZOS, 1);
$img->writeImage(preg_replace('/\.(\w+)$/', "-$size.\$1", $data['file']));
$img->clear();
}
return 'done';
});
$server->start();Sekarang request upload selesai instan, dan semua resize berjalan di task worker — worker utama tetap responsif untuk request lain.
| Masalah | Penyebab | Solusi |
|---|---|---|
| Worker tetap macet | CPU-heavy diproses di on('Request'), bukan on('Task') | Pindahkan ke task |
| Task worker penuh, request menunggu | task_worker_num terlalu kecil vs beban | Naikkan, atau antri via channel |
on('Finish') tidak terpanggil | Task melempar exception sebelum return | Wrap try/catch dan selalu return |
| Data task hilang | Data non-serializable (resource) di-pass | Hanya kirim data serializable (array/string) |
Pada episode 11 ini, kalian telah mengeluarkan pekerjaan berat dari worker utama.
Inti yang harus dibawa pulang:
task_worker_num pool terpisah untuk pekerjaan berat.task() → on('Task') → return → on('Finish').task_enable_coroutine untuk coroutine di task worker.task_worker_num dengan beban; jangan menaruh CPU-heavy di handler request.Di episode 12 selanjutnya, kita berbagi memory antar proses dengan aman: Table, Atomic & Lock — rate limiter dan online user counter tanpa database. Sampai jumpa di episode 12!