Maison >développement back-end >Tutoriel Python >Pourquoi `subprocess.Popen` avec `readline()` se bloque-t-il lors de la lecture à partir d'un script Ruby, et comment cela peut-il être corrigé ?

Pourquoi `subprocess.Popen` avec `readline()` se bloque-t-il lors de la lecture à partir d'un script Ruby, et comment cela peut-il être corrigé ?

Susan Sarandon
Susan Sarandonoriginal
2024-12-19 05:44:08347parcourir

Why does `subprocess.Popen` with `readline()` hang when reading from a Ruby script, and how can this be fixed?

Le sous-processus Python readlines() se bloque

Problème :

Lors de la lecture de la sortie d'un script Ruby à l'aide de subprocess.Popen et readline() en streaming, readline() se bloque indéfiniment et jamais renvoie.

Contexte :

L'objectif est de diffuser la sortie d'un fichier Ruby ligne par ligne, en l'imprimant sans mettre en mémoire tampon l'intégralité de la sortie.

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!")

Le script ruby_sleep.rb génère un message simple avec un délai de 2 secondes delay :

puts "hello"

sleep 2

puts "goodbye!"

Cause première :

readline() reste bloqué car le script Ruby génère des données sans lignes de fin (c'est-à-dire sans nouvelles lignes). Cela amène readline() à attendre indéfiniment qu'une nouvelle ligne complète la ligne.

Solutions :

Plusieurs solutions existent en fonction de la disponibilité de la plateforme :

  • Pour Linux :

    Utilisez pty du standard bibliothèque pour ouvrir un pseudo-terminal (tty) et activer la mise en mémoire tampon de ligne du côté de Ruby, en garantissant que chaque ligne se termine par une nouvelle ligne.

    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!")
  • Pour Linux- plates-formes basées :

    Utilisez pty de la bibliothèque standard et sélectionnez pour surveiller l'activité du descripteur de fichier maître, en vous assurant que les données sont lues dans un de manière non bloquante.

    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!")
  • Option multiplateforme :

    Utilisez stdbuf pour activer la mise en mémoire tampon de ligne 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()

Ces solutions permettent toutes la mise en mémoire tampon de ligne sur le Ruby côté, en veillant à ce que chaque ligne se termine par une nouvelle ligne, permettant à readline() de fonctionner correctement.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn