2014-05-05 8 views
9

Ho un comando Symfony Console che scorre su una collezione potenzialmente grande di elementi e svolge un'attività con ciascuno di essi. Poiché la raccolta può essere grande, il comando può richiedere molto tempo per essere eseguito (ore). Al termine del comando, vengono visualizzate alcune statistiche.Interruzione e ripresa di un comando di Symfony Console

Vorrei rendere possibile l'abortire il comando in un modo piacevole. In questo momento se lo interrompo (cioè con ctrl + c nella CLI), non esiste un riepilogo delle statistiche e non è possibile emettere i parametri necessari per riprendere il comando. Un altro problema è che il comando potrebbe essere terminato nel mezzo della gestione di un oggetto: sarebbe meglio se potesse terminare solo tra la gestione degli articoli.

Quindi c'è un modo per dire a un comando di "abortire il più presto possibile", o il comando ctrl + c deve essere interpretato come tale?

Ho provato a utilizzare l'evento ConsoleEvents::TERMINATE, sebbene i gestori per questo vengano attivati ​​solo al completamento del comando, non quando I ctrl + c la cosa. E non sono stato in grado di trovare ulteriori informazioni su come rendere tali comandi ripristinabili.

+0

Penso che rendendo il tuo input come input interattivo sarai in grado di risolvere il problema, ma non so esattamente come dovresti implementarlo in modo che * premendo un tasto specifico termini il comando e ti dia le statistiche * Questo link potrebbe aiutare [http://davidbu.ch/slides/20130613_techtalk_symfony-console.html] per creare il comando interattivo – Javad

+0

oppure puoi controllare il listener di eventi in comando e basare su quello che termina il tuo comando [http: // symfony .com/doc/current/components/console/events.html] – Javad

risposta

17

Questo è ciò che ha funzionato per me. È necessario chiamare pcntl_signal_dispatch prima che i gestori di segnale siano effettivamente eseguiti. Senza di esso, tutte le attività finiranno prima.

<?php 
use Symfony\Component\Console\Command\Command; 

class YourCommand extends Command 
{ 
    protected function execute(InputInterface $input, OutputInterface $output) 
    { 
     pcntl_signal(SIGTERM, [$this, 'stopCommand']); 
     pcntl_signal(SIGINT, [$this, 'stopCommand']); 

     $this->shouldStop = false; 

     foreach ($this->tasks as $task) 
     { 
      pcntl_signal_dispatch(); 
      if ($this->shouldStop) break; 
      $task->execute(); 
     } 

     $this->showSomeStats($output); 
    } 

    public function stopCommand() 
    { 
     $this->shouldStop = true; 
    } 
} 
+1

Questo è quello che sto cercando, grazie, ma un suggerimento: Devi impostare declare (tick = 1); http://php.net/manual/en/function.pcntl-signal.php#refsect1-function.pcntl-signal-changelog – createproblem

+0

Grazie, dal momento che PHP 7.1 è anche possibile attivare la gestione dei segnali asincroni con l'esecuzione 'pcntl_async_signals (true) ; 'http://php.net/manual/en/function.pcntl-async-signals.php – Karim

2

Si dovrebbe dare un'occhiata al trattamento del segnale RabbitMqBundle. Il suo metodo execute collega solo alcuni callback tramite la chiamata di funzione pcntl_signal(). Un caso comune dovrebbe essere più o meno così:

<?php 
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand as Command; 

class YourCommand extends Command 
{ 
    protected function execute(InputInterface $input, OutputInterface $output) 
    { 
     pcntl_signal(SIGTERM, array(&$this, 'stopCommand', $output)); 
     pcntl_signal(SIGINT, array(&$this, 'stopCommand', $output)); 
     pcntl_signal(SIGHUP, array(&$this, 'restartCommand', $output)); 

     // The real execute method body 
    } 

    public function stopCommand(OutputInterface $output) 
    { 
     $output->writeln('Stopping'); 

     // Do what you need to stop your process 
    } 

    public function restartCommand(OutputInterface $output) 
    { 
     $output->writeln('Restarting'); 

     // Do what you need to restart your process 
    } 
} 
+0

Grazie mille, questo e i commenti sopra riportati mi hanno fatto trovare la soluzione. L'esempio manca una chiamata a pcntl_signal_dispatch e ha anche un errore di sintassi: un array di callback ha solo due elementi. Non puoi importare argomenti dopo quelli. –

+0

@JeroenDeDauw, oops, questo era uno pseudo-codice, il mio male :) – kix

2

Le risposte sono più complesse di quanto debbano essere. Certo, puoi registrare gestori di segnali POSIX, ma se gli unici segnali che devono essere gestiti sono interrupt di base e simili, dovresti semplicemente definire un distruttore sul Comando.


class YourCommand extends Command 
{ 
    // Other code goes here. 

    __destruct() 
    { 
     $this->shouldStop = true; 
    } 
} 

un caso in cui si vorrebbe registrare un segnale di POSIX è per il segnale SIGCONT, che può gestire la ripresa di un processo che è stata interrotta (SIGSTOP).

Un altro caso sarebbe dove si desidera che ogni segnale si comporti in modo diverso; per la maggior parte, però, SIGINT e SIGTERM e una manciata di altri sarebbero registrati con la stessa operazione "OMG THE PROCESS HAS BEEN KILLED".

Oltre a questi esempi, la registrazione degli eventi di segnale non è necessaria. Questo è il motivo per cui esistono i distruttori.

È anche possibile estendere la classe Command di base di Symfony con un metodo __destruct, che fornisce automaticamente la pulizia per ogni comando; se un particolare comando richiede operazioni aggiuntive, basta sovrascriverlo.

Problemi correlati