2016-07-17 71 views
16

Sto lavorando a un'applicazione che ha periodicamente chiamato processi in background. Uno di questi è stato chiamato da cron, ma sto cercando qualcosa di più robusto, quindi lo sto convertendo per funzionare sotto Supervisor. (Probabilmente funzionerà per 10 minuti, durante i quali potrà rilevare il lavoro da eseguire o inattivo. Supervisor rigenererà automaticamente un'istanza pulita.)Posso contare sul fatto che register_shutdown_function() venga chiamato su SIGTERM, se pcntl_signal() è impostato?

Dal momento che Supervisor è meglio garantire che solo un numero specificato di istanze di qualcosa sono in esecuzione in parallelo, posso farla franca con loro più a lungo. Ciò significa tuttavia che i miei processi hanno maggiori probabilità di ricevere segnali di terminazione, direttamente da kill o perché sono stati arrestati tramite Supervisor. Sto quindi sperimentando come gestire questo in PHP.

Sembra che la soluzione di base è quella di utilizzare pcntl_signal() in questo modo:

declare(ticks = 1); 
pcntl_signal(SIGTERM, 'signalHandler'); 
pcntl_signal(SIGINT, 'signalHandler'); 

function signalHandler($signal) { 
    switch($signal) { 
     case SIGTERM: 
     case SIGINT: 
      echo "Exiting now...\n"; 
      exit(); 
    } 
} 

Tuttavia, ho diversi punti nel mio codice che potrebbe fare con un accurato trattamento di arresto. Un approccio è quello di indurre un operatore a chiamare queste varie cose, ma ciò richiederebbe un po 'di refactoring, che vorrei evitare. L'alternativa è quella di aggiungere pcntl_signal() ovunque ne abbia bisogno, ma sfortunatamente sembra che solo un gestore possa essere installato contemporaneamente.

Tuttavia, sembra che I potrebbe essere in grado di utilizzare register_shutdown_function(). Questo non lo fa trappola ^C o altri sigs di terminazione sulla propria, e il manuale è molto chiaro su questo punto: non saranno eseguiti

funzioni di arresto se il processo viene ucciso con un SIGTERM o SIGKILL segnale

Ciò che sorprende è che ho trovato che se utilizzo pcntl_signal() per fare solo un'uscita, allora i gestori di shutdown sono effettivamente chiamati. Inoltre, dal momento che uno può avere molti gestori di shutdown, questo risolve bene il mio problema - ogni classe nel mio codice che desidera gestire la terminazione con garbo può catturare e gestire il proprio spegnimento.

La mia domanda iniziale è, perché funziona? Ho provato a registrare una funzione di spegnimento senza un gestore di segnale, e questo non sembra essere chiamato, come dice il manuale. Immagino che il processo sia tenuto in vita da PHP per gestire il segnale, il che fa sì che vengano chiamati i gestori di shutdown?

Inoltre, posso fare affidamento su questo comportamento, quando il manuale lo mette in dubbio? Sto usando PHP 5.5, e non sto ancora cercando di aggiornare a PHP7. Quindi sarei interessato a sapere se funziona su 5.5 e 5.6, su una varietà di distribuzioni.

Ovviamente, se dovesse funzionare (o non funzionasse) su 7.0 e/o 7.1, sarebbe interessante anche questo - sono consapevole però che ticks will be handled differently in 7.1, quindi c'è una maggiore possibilità che questo abbia un comportamento diverso .

+0

Mi chiedo solo perché è necessario rigenerare i processi PHP (dato che non si perde memoria in modo casuale ;-))? – bwoebi

+0

Solo buone pratiche, @bwoebi - mi dà sicurezza che c'è sempre una nuova istanza in giro, anche se PHP e il mio sistema sono completamente privi di perdite ':-)'. – halfer

+0

Non sono d'accordo sul fatto che sia davvero una buona pratica. Per il tuo caso d'uso va bene, ma non appena hai bisogno di un socket raggiungibile in modo permanente [TCP/unix/...] (ad es. Per scaricare informazioni dal programma live), è brutto quando è intermittente irraggiungibile. – bwoebi

risposta

7

Le funzioni di spegnimento utente di PHP vengono richiamate durante la normale chiusura del processo, in qualche modo analoga alle funzioni registrate con atexit in altri linguaggi di programmazione. Un esplicito exit o un'uscita implicita alla fine dello script è una normale chiusura del processo.

La morte del segnale, tuttavia, è la terminazione del processo anormale. Il comportamento predefinito per SIGTERM (e il comportamento obbligatorio per SIGKILL) consiste nel terminare immediatamente il processo in esecuzione, senza eseguire alcun gestore di pulizia.

Quando si intercetta un SIGTERM con pcntl_signal, si sta installando un comportamento diverso e si offre l'opportunità di sperimentare la normale chiusura del processo.

Sì, puoi fare affidamento su questo comportamento. Mentre la principale voce di manuale non è esplicito, il resto della "Nota" si cita legge:

[Y] ou può utilizzare pcntl_signal() installare un gestore per un SIGTERM che utilizza exit() per terminare in modo pulito.

+0

Va bene, grazie per questo. Sì, ho visto quella nota, ma l'ho letta in modo diverso - "Le funzioni di spegnimento non vengono eseguite se il processo viene interrotto" è abbastanza esplicito, ma forse è solo una formulazione scadente. La parte successiva della nota sembrava implicare che si debba usare un gestore di segnali _instead_, piuttosto che in aggiunta a un gestore di shutdown. La tua interpretazione mi porterebbe a credere che questo comportamento possa essere invocato anche in PHP 7 - ti sembra corretto? – halfer

4

Oltre a pilcrows risposta corretta:

il gestore di segnale è necessaria per modificare il comportamento del segnale (cioè invece dell'azione predefinita). Qualunque cosa accada dopo è irrilevante.

È possibile utilizzare il gestore di segnale per eseguire solo la pulizia di emergenza e quindi chiamare posix_kill(getmypid(), SIGKILL); per forzare la terminazione senza eseguire la pulizia. Se si utilizza lo standard exit(), normalmente viene avviata la sequenza di spegnimento (vale a dire la chiamata di tutti i distruttori, i gestori di spegnimento, ecc.).

Quando dici in aggiunta a, non sei strettamente corretto. È necessario utilizzare il gestore di segnale invece e inoltre avere un gestore di arresto. [Riferendosi al vostro comment]

ricordiamo inoltre che chiamando exit() dopo la sequenza di spegnimento è stata avviata interromperà la parte corrente di esso (cioè quando si è attualmente all'interno di un gestore di arresto e exit() viene chiamato, tutti i successivi gestori di arresto vengono ignorati, ma ad es. i distruttori saranno ancora chiamati). Idealmente si avere un po 'di controllo contro questa:

register_shutdown_function(function() { $GLOBALS["#__in_shutdown"] = 1; }); 

e avere un controllo intorno exit():

if (empty($GLOBALS["#__in_shutdown"])) { 
    exit; 
} 

Solo nel caso si vuole essere veramente sicuro a meno che qualcuno spara SIGKILL su di esso.

Nota che 7.0 o 7.1 non cambieranno questo comportamento; tutto ciò che cambia in 7.1 è declare(ticks=1); superfluo, il comportamento esposto è sempre lo stesso.

+0

Bene, grazie.In relazione all'osservazione "aggiunta", probabilmente l'ho formulata male - intendevo che entrambi sono necessari se si vuole esplicitamente utilizzare i gestori di shutdown per ottenere più callback in una situazione sigterm/sigint. – halfer

+0

Per chiudere in modo pulito ogni parte del mio sistema, userei un gestore di dummy come per il post originale, nello script di root, e quindi un numero diverso da zero di gestori di shutdown impostati in varie classi, e nessuno di i gestori di shutdown chiamerebbero 'exit()', lasciandolo al gestore sig. Sembra un buon approccio? – halfer

+0

Intendevo aggiungere un if() attorno a 'exit()' nel gestore di segnale, in modo che la ricezione di un SIGTERM mentre si è già nella sequenza di spegnimento non lo interrompa. Per il resto, sì, sembra buono. – bwoebi

Problemi correlati