Busy süreçlerinin hata ayıklanması

Bazen php start.php status komutunu çalıştırdığımızda, busy durumunda olan süreçleri görebiliriz, bu da ilgili sürecin iş yükü işlediğini gösterir; normal koşullarda iş yükü tamamlandığında ilgili süreç idle durumuna dönecektir. Bu durum genellikle bir sorun oluşturmaz. Ancak, sürekli olarak busy durumda kalan bir süreç, süreç içindeki işin engellendiği veya sonsuz döngüye girdiği anlamına gelebilir. Aşağıdaki yöntemlerle bu durumları tespit edebilirsiniz.

strace+lsof komutları ile tespit

1. status da busy sürecin pid'sini bulun
php start.php status komutunu çalıştırdıktan sonra aşağıdaki gibi bir çıktı göreceksiniz:

Resimdeki busy durumundaki süreçlerin pid değerleri 11725 ve 11748'dir.

2. strace ile süreci takip etme
Bir süreç seçin, örneğin burada 11725 seçiyoruz, strace -ttp 11725 komutunu çalıştırın ve aşağıdaki çıktı göreceksiniz:

Sürecin sürekli olarak poll([{fd=16, events=.... sistem çağrısını döngü içinde çalıştığını görebiliyorsunuz; bu, fd'si 16 olan tanımlayıcının okunabilir bir olayı beklediğini gösterir, yani bu tanımlayıcıdan veri dönmesini beklemektedir.

Eğer hiçbir sistem çağrısı görüntülenmiyorsa, mevcut terminali koruyun, yeni bir terminal açın ve kill -SIGALRM 11725 (sürece bir alarm sinyali gönderin) komutunu çalıştırın; ardından strace terminalinin yanıt verip vermediğine, herhangi bir sistem çağrısında takılıp takılmadığına bakın. Eğer hala hiçbir sistem çağrısı görüntülenmiyor ise, programın büyük olasılıkla iş yükü içinde sonsuz döngüde olduğunu gösterir, sayfanın alt kısmındaki process'in uzun süre busy kalmasına neden olan diğer sebeplerden 2. maddeyi kontrol edin.

Eğer sistem epoll_wait veya select sistem çağrısında takılmışsa, bu normal bir durumdur ve süreç muhtemelen idle durumundadır.

3. lsof ile süreç tanımlayıcısını görüntüleme
lsof -nPp 11725 komutunu çalıştırdığınızda aşağıdaki gibi bir çıktı görürsünüz:

Tanımlayıcı 16, son satırdaki 16u kaydına karşılık gelmektedir. Gördüğünüz gibi fd=16 tanımlayıcısı bir tcp bağlantısıdır, uzak adresi ise 101.37.136.135:80'dir; bu da sürecin bir http kaynağına eriştiğini göstermektedir. Sürekli olarak döngüde bulunan poll([{fd=16, events=.... ifadesi, http sunucusunun veri döndürmesini beklediği için busy durumuna girdiğini açıklamaktadır.

Çözüm:
Sürecin nerede takıldığını öğrendikten sonra, çözmek kolaydır. Yukarıdaki örnekte, iş yükünün curl çağrısı yaptığı ve ilgili url'nin uzun süre veri döndürmediği tespit edilmiştir. Bu durumda, url sağlayıcısına ulaşıp neden bozulduğunu sormak gerekebilir. Aynı zamanda, curl çağrısı sırasında zaman aşımı parametreleri eklemek, örneğin eğer iki saniye içinde dönüş yoksa zaman aşımına uğramasını sağlamak gerekir, böylece uzun süreli bir engel oluşmaz (bu durumda süreç yaklaşık iki saniye boyunca busy durumunda kalabilir).

Sürecin uzun süre busy kalmasının diğer nedenleri

Sadece süreç engellendiğinde veya busy olduğunda değil, aynı zamanda aşağıdaki nedenler de sürecin busy durumunda olmasına neden olabilir.

1. İş yükünde kritik hatalar sürecin sürekli çıkmasına neden olur
Belirti: Bu durumda sistem yükünün yüksek olduğunu görebiliriz; status'taki load average 1 veya daha yüksek. Sürecin exit_count sayısının oldukça yüksek olduğunu ve sürekli olarak arttığını görebiliriz.
Çözüm: php start.php start komutunu (-d eklemeden) debug modunda çalıştırarak iş yükü hatalarını kontrol edin, hataları giderin.

2. Kod içinde sonsuz döngü var
Belirti: top içinde busy süreçlerin yüksek cpu tüketimi olduğunu görebiliriz; strace -ttp pid komutunu çalıştırdığınızda herhangi bir sistem çağrısı bilgisi görmüyorsunuz.
Çözüm: Kuş kardeşin makalesine istinaden gdb ve php kaynağı ile tespit etmek aşamaları genel olarak şöyle sıralanabilir:

  1. php -v ile versiyonu kontrol et.
  2. İlgili php versiyonunun kaynak kodunu indir.
  3. gdb --pid=busy sürecin pid'si
  4. source php kaynak yolu/.gdbinit
  5. zbacktrace komutunu çalıştırarak çağrı yığınını yazdırın.
    Son adımda, php kodunun o anki yürütme çağrı yığınını görebilirsiniz; bu da php kodunun sonsuz döngüde olduğu yerdir.
    Not: Eğer zbacktrace çağrı yığınını yazdırmazsa, php'nin derleme sırasında -g parametresi kullanılmamış olabilir; bu durumda php'yi yeniden derlemeli ve workerman'ı yeniden başlatmalısınız.

3. Sonsuz timer ekleme
İş yükü kodunda sürekli timer eklenip silinmiyorsa, süreç içindeki timer sayısı artar ve sonuç olarak sürecin sonsuz döngüde timer çalıştırmasına neden olur. Örneğin aşağıdaki kod:

$worker = new Worker;
$worker->onConnect = function($con){
    Timer::add(10, function(){});
};
Worker::runAll();

Yukarıdaki kod bir istemci bağlantısı geldiğinde bir timer ekleyecek, fakat iş yükü kodunun içinde timer silme mantığı yok, zaman geçtikçe süreç içinde sürekli olarak timer artacak ve sonuçta sürecin sonsuz döngüde timer çalıştırmasına neden olacak.
Doğru kod:

$worker = new Worker;
$worker->onConnect = function($con){
    $con->timer_id = Timer::add(10, function(){});
};
$worker->onClose = function($con){
    Timer::del($con->timer_id);
};
Worker::runAll();