Debugging di processi busy

A volte, eseguendo il comando php start.php status, possiamo vedere processi in stato di busy, il che significa che il processo corrispondente sta elaborando un lavoro. Normalmente, una volta completato il lavoro, il processo torna in stato di idle. In linea generale, questo non dovrebbe creare problemi. Tuttavia, se il processo rimane continuamente in stato di busy senza tornare idle, ciò indica che ci sono blocchi o cicli infiniti nel processo, e possiamo utilizzare i seguenti metodi per identificare il problema.

Localizzazione con i comandi strace e lsof

1. Trovare il pid del processo busy in status
Dopo aver eseguito php start.php status, il risultato visualizza quanto segue

Il pid del processo in stato di busy è 11725 e 11748.

2. Tracciare il processo con strace
Selezionare un pid del processo (qui scegliamo 11725), quindi eseguire strace -ttp 11725, che mostra quanto segue

Possiamo vedere che il processo è in un ciclo continuo di chiamate di sistema poll([{fd=16, events=....; questo indica che sta aspettando eventi di lettura per il descrittore fd 16, cioè sta aspettando che questo descrittore restituisca dati.

Se non vengono visualizzate chiamate di sistema, mantenere il terminale attuale aperto, aprirne un altro e eseguire kill -SIGALRM 11725 (inviare un segnale di allerta al processo), quindi controllare se il terminale di strace risponde e se è bloccato su qualche chiamata di sistema. Se non vengono ancora visualizzate chiamate di sistema, è probabile che il programma sia in un ciclo infinito. Consultare la seconda voce "Altre cause che possono provocare un lungo stato busy" nella parte inferiore di questa pagina per ulteriori informazioni.

Se il sistema è bloccato su una chiamata di sistema epoll_wait o select, è una situazione normale, il che indica che il processo è già in stato idle.

3. Visualizzare i descrittori di processo con lsof
Eseguire lsof -nPp 11725, che mostra quanto segue

Il descrittore 16 corrisponde alla registrazione di 16u (l'ultima riga), che mostra che il descrittore fd=16 è una connessione tcp, con l'indirizzo remoto 101.37.136.135:80, il che indica che il processo dovrebbe essere impegnato nell'accesso a una risorsa http. Il ciclo poll([{fd=16, events=.... è continuamente in attesa che il server http restituisca dati, il che spiega perché il processo è in stato busy.

Soluzione:
Sapendo dove è bloccato il processo, ora è più facile risolvere il problema. Ad esempio, come localizzato in precedenza, il problema potrebbe essere dovuto al fatto che l'attività sta chiamando curl e l'url non restituisce dati per lungo tempo, causando attesa continua nel processo. In questo caso, puoi contattare il fornitore dell'url per identificare la causa della lentezza nella restituzione, e dovresti anche aggiungere un parametro di timeout alla chiamata curl, come ad esempio un timeout di 2 secondi, per evitare blocchi prolungati (in questo modo, il processo potrebbe mostrare uno stato busy di circa 2 secondi).

Altre cause di lungo stato busy del processo

Oltre al blocco del processo che provoca un stato di busy, ci sono altre cause che possono portare il processo a essere in stato busy.

1. Errori critici nell'attività che causano continui arresti del processo
Manifestazione: In questo caso, si può osservare un carico di sistema elevato, con load average in status pari a 1 o superiore. Si può notare che il numero di exit_count del processo è molto alto e continua a crescere.
Soluzione: Eseguire in modalità debug (php start.php start senza -d) per vedere gli errori dell'attività e risolvere gli errori.

2. Cicli infiniti nel codice
Manifestazione: Dall'output di top, possiamo vedere che il processo busy utilizza un'alta percentuale di CPU e il comando strace -ttp pid non stampa alcuna informazione relativa alle chiamate di sistema.
Soluzione: Fare riferimento all'articolo di Niao Ge attraverso gdb e il codice sorgente php, i passaggi sono riassunti come segue:

  1. Controllare la versione con php -v
  2. Scaricare il codice sorgente della versione php corrispondente
  3. Eseguire gdb --pid=pid del processo busy
  4. Eseguire source php sourc path/.gdbinit
  5. Stampare lo stack di chiamate con zbacktrace.
    Nell'ultimo passaggio, è possibile vedere lo stack di chiamate attuale nel codice php, ovvero il punto di ciclo infinito nel codice php.
    Nota: Se zbacktrace non stampa lo stack di chiamate, potrebbe essere che la tua compilazione di php non includeva il parametro -g, quindi dovresti ricompilare php e poi riavviare workerman per localizzare il problema.

3. Aggiunta infinita di timer
Il codice dell'attività continua ad aggiungere timer senza mai rimuoverli, causando un accumulo di timer nel processo, portando infine il processo a eseguire all'infinito i timer. Ad esempio, il seguente codice:

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

Il codice sopra aumenterà un timer quando ci sarà una connessione da un client, ma all'interno dell'intero codice dell'attività non ci sono logiche per rimuovere i timer, quindi, col passare del tempo, il numero di timer nel processo continuerà a crescere, portando infine a un'esecuzione infinita dei timer e a uno stato busy.
Il codice corretto:

$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();