Taverna /dev/All

Subprocess + popen + Thread ?

Olá. Estou usando este post como referência, mas não acho que está me ajudando.

Vendo este outro post, consegui fazer com que o Python disparasse meus comandos no Linux, mas percebi que eu não tenho controle sobre como encerrá-los quando eu quiser. De fato, eu preciso lidar com múltiplos eventos recebendo via MQTT e o método que usei me limitou quanto a isso.

Li sobre Popen, subprocess e um possível uso de Threads (em todas essas coisas eu não tenho conhecimento), mas realmente não sei se está certo o que li. A dúvida é:

Como posso disparar um comando no Linux e manter controle sobre ele?

Meu cenário atual é: Eu me inscrevo em um tópico MQTT, e ao receber mensagem, pego os parâmetros que recebi e executo um comando no Linux. Após 10 minutos ou após o recebimento de uma mensagem informando o fim do evento, devo matar o comando(nesse caso, o processo) e continuar na espera de outros eventos.

Estou iniciando em Python por agora :]

E aí, FearX, na paz?

Problema legal esse. Existe um parâmetro timeout (https://docs.python.org/3/library/subprocess.html#subprocess.TimeoutExpired.timeout) para o subprocess, você testou ele? Olha isso:

➤ python3
Python 3.6.7 (default, Oct 22 2018, 11:32:17) 
[GCC 8.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import subprocess
>>> subprocess.run(['find', '/usr', '-name', 'bla'],  timeout=1, stdout=subprocess.DEVNULL) 
Traceback (most recent call last):
  File "/usr/lib/python3.6/subprocess.py", line 405, in run
    stdout, stderr = process.communicate(input, timeout=timeout)
  File "/usr/lib/python3.6/subprocess.py", line 843, in communicate
    stdout, stderr = self._communicate(input, endtime, timeout)
  File "/usr/lib/python3.6/subprocess.py", line 1540, in _communicate
    self.wait(timeout=self._remaining_time(endtime))
  File "/usr/lib/python3.6/subprocess.py", line 1449, in wait
    raise TimeoutExpired(self.args, timeout)
subprocess.TimeoutExpired: Command '['find', '/usr', '-name', 'bla']' timed out after 0.9998030720016686 seconds

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.6/subprocess.py", line 410, in run
    stderr=stderr)
subprocess.TimeoutExpired: Command '['find', '/usr', '-name', 'bla']' timed out after 1 seconds

O que significa que você deverá tratar essa exceção em seu código (esse comando, em minha máquina, leva muito mais que um segundo, veja que ele foi abortado quando chegou bem perto desse limite).

Mas tem outro detalhe: desse jeito você executará apenas uma tarefa por vez. Para executar várias, pode usar o os.fork().

E se a coisa for pesada mesmo, usar o forkserver do módulo multiprocessing, e combinar tudo isso pra ter um certo controle sobre os processos filhos.

Oi, Paulo! Obrigadão pela resposta. O lance é que eu preciso de múltiplos processos em algo parecido com Thread, pois tenho que ficar escutando o tópico no MQTT. Dessa forma, eu consigo matar os processos quando eu quiser?

Esse “matar quando eu quiser” é que pode deixar a coisa complicada :slight_smile:, mas se for só tempo de execução, fica bem fácil. Quais são esses critérios?

O script rodará o tempo todo, recebendo eventos MQTT e disparando o comando pro FFMPEG gravar a camera. Eu recebo dois tipos de eventos no MQTT: Inicio e parada. Quando receber o “parada” eu tenho que matar o processo. Se eu não recebo o stop em 10 minutos depois de receber o start, eu tenho que matar o processo tbm. Acho que é basicamente isso!

Esqueci de avisar que vou executar multiplos comandos ao mesmo tempo!

Então você vai ter que elaborar uma certa inteligência aí, pra saber qual processo matar quando receber a ordem. Como você identifica a task a ser morta quando recebe o “parada”?

Vou assumir que de alguma forma você identifica o arquivo que receberá a gravação. Pode usar essa identificação como chave em um dicionário que tenha o PID do processo filho, por exemplo:

{
    'security_20190110_221000_0001.mpg': 119,
    'security_20190110_221010_0001.mpg': 765,
}

Quando receber o “parada”, basta gerar novamente o nome único do arquivo e mandar um kill pro pid, por exemplo.

Claro, eu assumi que é possível determinar, tanto no início, como no final, um nome único de arquivo. Mas o importante é estabelecer um critério que produza o mesmíssimo resultado no “início” e na “parada”.

E o problema dos 10 minutos você resolve com o parâmetro timeout do subprocess.

Essa é minha sugestão, mas certamente existem muitas outras e melhores. O importante é dominar esse fluxo aí, pois as ferramentas são tranquilas.

1 Curtida

Como posso fazer um timeout nesse subprocess?

def gravacao(self, id_camera, rtsp, dir, filename, cameras_running):
    try:
        cameras_running[id_camera] = subprocess.Popen(
            ['ffmpeg', '-rtsp_transport', 'tcp', '-i', rtsp, '-acodec', 'copy', '-vcodec', 'copy',
             dir + '/' + filename], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        print("Start recording on camera:", id_camera)
    except:
        return False

Quero que após X minutos esse processo morra. Mas não tô entendendo como usar/fazer o timeout.

Testei com:

        try:
            cameras_running[id_camera].communicate(timeout=10)
        except TimeoutError:
            cameras_running[id_camera].kill()
            cameras_running[id_camera].communicate()

Mas não funcionou, ele espera 10 segundos e não mata a gravação. Aí, fui ler sobre o os.fork, e sinceramente não entendi ele.

[]'s

1 Curtida

Solução: O FFMPEG tem parâmetro de timeout! xD

1 Curtida

Uia, nem deu tempo de eu dar uma bicada! :slight_smile:

1 Curtida

itexto