UNIX 102: pipes
Teil 2 von „Schlaflos in Oldenburg“:
from subprocess import Popen, PIPE
producer = Popen(("producer",), stdout=PIPE)
consumer = Popen(("consumer",), stdin=producer.stdout)
ret = producer.wait()
Wo ist das Problem?
Ich hole dazu ein bisschen weiter aus:
Klassische UNIX-Programme lesen von STDIN und schreiben nach STDOUT. Für den Verbraucher oben macht es keinen Unterschied, ob er aus einer Datei, dem Terminal oder über eine Pipe von einem anderen Prozeß ließt: Das Ende der Eingabe erkennt er daran, daß stdin.read()
die leere Zeichenkette für End-of-File zurückliefert und sich dann (vermutlich) normal beendet.
Für den Erzeuger sieht es allerdings ein bisschen anders aus: Solange der nur eine endliche Ausgabe produziert, wird er sich damit auch nach endlicher Zeit beenden.
Das muß aber nicht so sein: Klassiker wie yes
liefern eine unendlich lange Ausgabe und beenden sich damit nie, was im obigen Szenario dann dazuführen würden, daß der Prozeß ewig läuft.
Hier spielen zwei Dinge eine Rolle:
- Die Pipe ist im Kernel implementiert und benötigt dort Speicherplatz, der durch
/proc/sys/fs/pipe-max-size
limitiert ist. Sobald die Pipe gefüllt ist, wird der Erzeuger erstmal angehalten, bis wieder Platz frei ist. - Dem Erzeuger wird per
SIGPIPE
mitgeteilt, wenn kein Prozeß mehr existiert, der sich für seine Ausgabe interessiert. Da der Standard-Signal-Handler für SIGPIPE “Term” ist, wird der Prozeß dadurch dann beendet.
Das Problem ist das kleine Wörtchen “kein”: Der Vater-Prozeß, der die beiden Kindprozesse gestartet hat, hat selber auch noch eine Referenz auf die PIPE offen und könnte damit selber als Erzeuger oder Verbraucher auftreten. D.h. der Erzeuger produziert munter weiter Daten bis die PIPE voll ist. Da der Vaterprozeß auf die Beendigung der Erzeugers wartet, befinden sich damit beide Prozesse in einem klassischen Deadlock.
Deswegen muß unbedingt ein producer.stdout.close()
for dem wait()
gemacht werden, damit der Verbraucher der einzige Leser wird.
Merke:
- Benutze
close_fds=True
, um garantiert dafür zu sorgen, daß der aufgerufene Prozeß nur STDIN, STDOUT, und STDERR vom Vaterprozeß erbt und nicht noch irgendwelche offenen PIPEs. Für den Fall der Python-PIPE-Pipeline muß das aber nicht explizit gesetzt werden. - Schließe die Kopien der PIPEs im Vaterprozeß, wenn du Pipelines von mehreren Kindprozessen verwendest.
- Für die C-Programmierer: Schaut euch bitte
fnctl(fd, FD_CLOEXEC, ...)
bzw.pipe2(int[], O_CLOEXEC)
an.