web-dev-qa-db-fra.com

Python sous-processus readlines () se bloque

La tâche que j'essaie d'accomplir est de diffuser un fichier Ruby et d'imprimer la sortie. ( [~ # ~] note [~ # ~] : Je ne veux pas tout imprimer en même temps)

main.py

from subprocess import Popen, PIPE, STDOUT

import pty
import os

file_path = '/Users/luciano/Desktop/Ruby_sleep.rb'

command = ' '.join(["Ruby", file_path])

master, slave = pty.openpty()
proc = Popen(command, bufsize=0, Shell=True, stdout=slave, stderr=slave, close_fds=True)     
stdout = os.fdopen(master, 'r', 0)

while proc.poll() is None:
    data = stdout.readline()
    if data != "":
        print(data)
    else:
        break

print("This is never reached!")

Ruby_sleep.rb

puts "hello"

sleep 2

puts "goodbye!"

Problème

La diffusion du fichier fonctionne correctement. La sortie Bonjour/Au revoir est imprimée avec un délai de 2 secondes. Exactement comme le script devrait fonctionner. Le problème est que readline () se bloque à la fin et ne se ferme jamais. Je n'atteins jamais le dernier tirage.

Je sais qu'il y a beaucoup de questions comme celle-ci ici un stackoverflow mais aucune d'elles ne m'a fait résoudre le problème. Je ne suis pas dans le sous-processus, alors donnez-moi une réponse plus pratique/concrète.

Cordialement

éditer

Correction du code involontaire. (rien à voir avec l'erreur réelle)

27
vermin

Je suppose que vous utilisez pty pour les raisons décrites dans Q: Pourquoi ne pas simplement utiliser un tuyau (popen ())? (toutes les autres réponses jusqu'à présent ignorent votre "REMARQUE: je ne veux pas tout imprimer en même temps" ).

pty est Linux uniquement comme dit dans la documentation :

Étant donné que la gestion des pseudo-terminaux dépend fortement de la plate-forme, il existe du code pour le faire uniquement pour Linux. (Le code Linux est censé fonctionner sur d'autres plates-formes, mais n'a pas encore été testé.)

On ne sait pas dans quelle mesure cela fonctionne sur d'autres systèmes d'exploitation.

Vous pouvez essayer pexpect:

import sys
import pexpect

pexpect.run("Ruby ruby_sleep.rb", logfile=sys.stdout)

Ou stdbuf pour activer la mise en mémoire tampon des lignes en mode non interactif:

from subprocess import Popen, PIPE, STDOUT

proc = Popen(['stdbuf', '-oL', 'Ruby', 'Ruby_sleep.rb'],
             bufsize=1, stdout=PIPE, stderr=STDOUT, close_fds=True)
for line in iter(proc.stdout.readline, b''):
    print line,
proc.stdout.close()
proc.wait()

Ou en utilisant pty de stdlib basé sur @ Antti Haapala's answer :

#!/usr/bin/env python
import errno
import os
import pty
from subprocess import Popen, STDOUT

master_fd, slave_fd = pty.openpty()  # provide tty to enable
                                     # line-buffering on Ruby's side
proc = Popen(['Ruby', 'Ruby_sleep.rb'],
             stdin=slave_fd, stdout=slave_fd, stderr=STDOUT, close_fds=True)
os.close(slave_fd)
try:
    while 1:
        try:
            data = os.read(master_fd, 512)
        except OSError as e:
            if e.errno != errno.EIO:
                raise
            break # EIO means EOF on some systems
        else:
            if not data: # EOF
                break
            print('got ' + repr(data))
finally:
    os.close(master_fd)
    if proc.poll() is None:
        proc.kill()
    proc.wait()
print("This is reached!")

Les trois exemples de code affichent "bonjour" immédiatement (dès que le premier EOL est vu).


laissez l'ancien exemple de code plus compliqué ici car il peut être référencé et discuté dans d'autres articles sur SO

Ou en utilisant pty basé sur @ la réponse d'Antti Haapala :

import os
import pty
import select
from subprocess import Popen, STDOUT

master_fd, slave_fd = pty.openpty()  # provide tty to enable
                                     # line-buffering on Ruby's side
proc = Popen(['Ruby', 'Ruby_sleep.rb'],
             stdout=slave_fd, stderr=STDOUT, close_fds=True)
timeout = .04 # seconds
while 1:
    ready, _, _ = select.select([master_fd], [], [], timeout)
    if ready:
        data = os.read(master_fd, 512)
        if not data:
            break
        print("got " + repr(data))
    Elif proc.poll() is not None: # select timeout
        assert not select.select([master_fd], [], [], 0)[0] # detect race condition
        break # proc exited
os.close(slave_fd) # can't do it sooner: it leads to errno.EIO error
os.close(master_fd)
proc.wait()

print("This is reached!")
26
jfs

Je ne sais pas ce qui ne va pas avec votre code, mais ce qui suit semble fonctionner pour moi:

#!/usr/bin/python

from subprocess import Popen, PIPE
import threading

p = Popen('ls', stdout=PIPE)

class ReaderThread(threading.Thread):

    def __init__(self, stream):
        threading.Thread.__init__(self)
        self.stream = stream

    def run(self):
        while True:
            line = self.stream.readline()
            if len(line) == 0:
                break
            print line,


reader = ReaderThread(p.stdout)
reader.start()

# Wait until subprocess is done
p.wait()

# Wait until we've processed all output
reader.join()

print "Done!"

Notez que je n'ai pas Ruby installé et que je ne peux donc pas vérifier votre problème réel. Fonctionne bien avec ls, cependant.

3
Florian Brucker

Fondamentalement, ce que vous regardez ici est une condition de concurrence entre votre proc.poll() et votre readline(). Étant donné que l'entrée sur le descripteur de fichier master n'est jamais fermée, si le processus tente de faire une readline() dessus après que le processus Ruby a terminé la sortie, il ne sera jamais rien à lire, mais le canal ne se fermera jamais. Le code ne fonctionnera que si le processus Shell se ferme avant que votre code n'essaye une autre readline ().

Voici la chronologie:

readline()
print-output
poll()
readline()
print-output (last line of real output)
poll() (returns false since process is not done)
readline() (waits for more output)
(process is done, but output pipe still open and no poll ever happens for it).

La solution facile consiste à simplement utiliser le module de sous-processus comme il le suggère dans la documentation, pas en conjonction avec openpty:

http://docs.python.org/library/subprocess.html

Voici un problème très similaire à approfondir:

L'utilisation du sous-processus avec select et pty se bloque lors de la capture de la sortie

2
jmh

Essaye ça:

proc = Popen(command, bufsize=0, Shell=True, stdout=PIPE, close_fds=True)
for line in proc.stdout:
    print line

print("This is most certainly reached!")

Comme d'autres l'ont noté, readline() se bloquera lors de la lecture des données. Il le fera même lorsque votre processus enfant sera mort. Je ne sais pas pourquoi cela ne se produit pas lors de l'exécution de ls comme dans l'autre réponse, mais peut-être que l'interpréteur Ruby détecte qu'il écrit sur un PIPE et qu'il ne le fera donc pas se ferme automatiquement.

1
Hans Then