Mengotomasi pekerjaan berulang dengan Swoole Timer tick dan after, membangun penjadwalan ala cron di dalam server, lalu memahami Swoole Process untuk fork, pipe, dan inter-process communication antar worker.

Setelah di episode 9 kita membangun aggregator API yang memanggil banyak upstream secara parallel — pada episode kali ini kita berurusan dengan waktu dan proses. Dua hal yang sering diabaikan developer PHP-FPM, karena FPM tidak pernah memberi kalian kontrol atas keduanya.
Mengapa episode ini penting? Karena aplikasi realtime tidak bisa menunggu cron menit demi menit. Membersihkan koneksi basi, mengirim heartbeat, merotasi log, menjadwalkan batch — semuanya harus hidup di dalam server. Dan saat beban tumbuh, kalian juga harus tahu cara menambah proses di luar arsitektur worker standar.
Swoole menyediakan dua jenis timer:
| Timer | Fungsi | Analogi |
|---|---|---|
tick($ms, $cb) | Eksekusi berulang tiap $ms milidetik | cron * * * * * (setiap interval) |
after($ms, $cb) | Eksekusi sekali setelah $ms milidetik | sleep non-blocking + eksekusi |
Contoh dasar:
use Swoole\Timer;
// Setiap 2 detik, log counter
$timerId = Timer::tick(2000, function () {
echo date('H:i:s') . " tick\n";
});
// Setelah 10 detik, hentikan timer tick
Timer::after(10000, function () use ($timerId) {
Timer::clear($timerId);
echo "Timer dihentikan\n";
});Timer::clear($id) membatalkan timer — sangat penting untuk mencegah kebocoran timer saat worker direcycle atau koneksi ditutup.
Timer memungkinkan penjadwalan yang lebih fleksibel daripada cron. Pola umumnya: hitung selisih ke target berikutnya, lalu after():
use Swoole\Timer;
function scheduleNextHourly(callable $job): void
{
$now = time();
$next = (intdiv($now, 3600) + 1) * 3600; // jam berikutnya tepat
$delayMs = ($next - $now) * 1000;
Timer::after($delayMs, function () use ($job) {
$job();
scheduleNextHourly($job); // jadwalkan lagi
});
}
scheduleNextHourly(fn () => echo "Laporan jam " . date('H') . "\n");Pola ini lebih akurat daripada men-set tick(3600000) karena tidak menggeser fase seiring waktu. Untuk kebutuhan kompleks, pertimbangkan library penjadwalan seperti cron-expression + Timer.
Note
Timer di Swoole berjalan di dalam worker yang mendaftarkannya. Bila aplikasi kalian memakai banyak worker, timer ganda bisa terjadi (setiap worker mendaftarkan timer sendiri). Untuk job sekali eksekusi global, jadwalkan hanya di satu worker (misal cek worker_id === 0 di on('WorkerStart')), atau gunakan distributed lock (episode 22).
Swoole\Process memberi kalian fork proses eksternal dari dalam server. Ini berguna untuk tugas yang tidak cocok di worker biasa maupun task worker — misal consumer MQ, atau proses daemon pelengkap.
use Swoole\Process;
$process = new Process(function (Process $worker) {
$worker->write("Halo dari child!\n");
});
$pid = $process->start();
echo "Parent menerima: " . $process->read();
$process->wait();Alur eksekusi: start() mem-fork — kode di callback berjalan di proses anak, sedangkan start() di proses induk mengembalikan PID anak. read()/write() berkomunikasi lewat pipe.
Server Swoole juga bisa mengelola proses custom lewat addProcess() — proses ini hidup di bawah Master dan ikut di-manage (restart saat mati):
<?php
use Swoole\Server;
use Swoole\Process;
$server = new Server('0.0.0.0', 9501);
$custom = new Process(function (Process $proc) use ($server) {
while (true) {
$message = $proc->read();
if ($message !== false) {
$server->sendMessage("Custom: $message", 0); // kirim ke worker 0
}
}
}, false, SOCK_DGRAM);
$server->addProcess($custom);
$server->on('Receive', function ($server, $fd, $reactorId, $data) {
echo "Terima: $data\n";
});
$server->start();Proses custom sangat berguna untuk consumer antrian yang berjalan terus-menerus di samping server — kita pakai lagi di episode 14 (message brokers).
Worker-worker Swoole perlu saling bicara dalam beberapa skenario:
| Metode | Pemakaian |
|---|---|
sendMessage($data, $workerId) | Kirim pesan antar worker (diproses di on('PipeMessage')) |
Server->task() | Kirim pekerjaan ke task worker (episode 11) |
| Pipe (Unix socket) | Komunikasi induk ↔ child custom process |
| Redis/antrian eksternal | Komunikasi lintas node (episode 22) |
Contoh broadcast ke semua worker dari satu worker:
use Swoole\Server;
$server->on('WorkerStart', function (Server $server, int $workerId) {
if ($workerId === 0) {
// worker pertama mengumumkan ke worker lain
for ($i = 1; $i < $server->setting['worker_num']; $i++) {
$server->sendMessage('reload-cache', $i);
}
}
});
$server->on('PipeMessage', function (Server $server, int $srcWorkerId, $message) {
echo "Worker {$srcWorkerId}: $message\n";
});Warning
Jangan pakai variabel global untuk komunikasi antar proses — ingat pelajaran episode 2: memory terisolasi per process. sendMessage/PipeMessage adalah jalan yang benar untuk komunikasi antar worker; variabel hanya berlaku di dalam satu proses.
| Masalah | Penyebab | Solusi |
|---|---|---|
| Timer ganda antar worker | Setiap worker mendaftarkan timer sendiri | Cek worker_id === 0, atau pakai lock |
| Process child "mencuri" listener | addProcess dijalankan tanpa wait() | Pastikan parent me-wait() atau kelola daemon |
| Pipe terblokir | read() tanpa data dan tanpa timeout | Set read() dengan timeout, atau pola SOCK_DGRAM |
| Zombie process | Child selesai tapi tidak di-wait() | Panggil $process->wait() setelah start() |
Pada episode 10 ini, kalian telah menguasai timer dan pengelolaan proses.
Inti yang harus dibawa pulang:
Timer::tick untuk interval; Timer::after untuk sekali; Timer::clear untuk membatalkan.after() — fase tidak bergeser.Swoole\Process untuk fork; addProcess() menempatkan proses custom di bawah Master.sendMessage/on('PipeMessage').Di episode 11 selanjutnya, kita membawa pekerjaan berat keluar dari worker: Task Worker untuk CPU-bound work — image processing, PDF, dan pengiriman email async lewat task pool. Sampai jumpa di episode 11!