Erzwingen Sie das Löschen des Ausgabepuffers im laufenden Programm

20

Ich habe ein lang laufendes Python-Skript, das regelmäßig Daten an die Standardausgabe ausgibt, die ich mit etwas aufgerufen habe:

python script.py > output.txt

Dieses Skript wurde für eine Weile ausgeführt und ich möchte es mit Ctrl+ stoppen, Caber nichts von seiner Ausgabe verlieren. Leider habe ich bei der Implementierung des Skripts vergessen, den Puffer nach jeder Ausgabezeile mit etwas wie dem zu leeren sys.stdout.flush()(der zuvor vorgeschlagenen Lösung zum Erzwingen des Löschens der Ausgabe). Wenn ich also jetzt Ctrl+ Caufrufe, verliere ich alle meine Ausgaben.

Wenn Sie sich fragen, ob es eine Möglichkeit gibt, mit einem laufenden Python-Skript (oder allgemeiner mit einem laufenden Prozess) zu interagieren, um es zu zwingen, seinen Ausgabepuffer zu leeren. Ich frage nicht, wie das Skript bearbeitet und erneut ausgeführt werden soll, damit es richtig geleert wird. Bei dieser Frage geht es speziell um die Interaktion mit einem laufenden Prozess (und in meinem Fall darum, die Ausgabe meiner aktuellen Codeausführung nicht zu verlieren).

josliber
quelle

Antworten:

18

Wenn man diese Daten wirklich haben möchte, würde ich vorschlagen, den gdb- Debugger an den Python-Interpreter anzuhängen, die Task vorübergehend anzuhalten, fsync(1)( stdout ) aufzurufen , sich von ihr zu lösen (den Prozess fortzusetzen) und die Ausgabedatei zu durchsuchen.

Suchen Sie /proc/$(pidof python)/fdnach gültigen Dateideskriptoren. $(pidof x)Gibt die PID des Prozesses mit dem Namen ' x' zurück.

# your python script is running merrily over there.... with some PID you've determined.
#
# load gdb
gdb
#
# attach to python interpreter (use the number returned by $(pidof python))
attach 1234
#
# force a sync within the program's world (1 = stdout, which is redirected in your example)
call fsync(1)
#
# the call SHOULD have returned 0x0, sync successful.   If you get 0xffffffff (-1), perhaps that wasn't stdout.  0=stdin, 1=stdout, 2=stderr
#
# remove our claws from poor python
detach
#
# we're done!
quit

Ich habe diese Methode verwendet, um Arbeitsdir's zu ändern, Einstellungen on the fly zu ändern ... viele Dinge. Leider können Sie nur Funktionen aufrufen, die im laufenden Programm definiert sind, fsyncfunktioniert aber gut.

(Mit dem Befehl ' info functions' gdb werden alle verfügbaren Funktionen aufgelistet. Seien Sie jedoch vorsichtig. Sie führen LIVE auf einem Prozess aus.)

Es gibt auch den Befehl peekfd(im psmiscPaket unter Debian Jessie und anderen enthalten), mit dem Sie sehen können, was sich in den Puffern eines Prozesses versteckt. Auch hier /proc/$(pidof python)/fdwerden Ihnen gültige Dateideskriptoren angezeigt, die Sie als Argumente für peekfd angeben können.

Wenn Sie sich nicht an -uPython erinnern , können Sie einem Befehl immer das Präfix stdbuf(in coreutils, bereits installiert) voranstellen , um stdin / stdout / stderr wie gewünscht auf ungepuffert, zeilengepuffert oder blockgepuffert zu setzen:

stdbuf -i 0 -o 0 -e 0 python myscript.py > unbuffered.output

Natürlich, man pagessind deine Freunde, hey! Vielleicht kann auch hier ein Alias ​​nützlich sein.

alias python='python -u'

Jetzt verwendet Ihre Python immer -ufür alle Ihre Kommandozeilenvorgänge!

Lornix
quelle
5

Stellen Sie zunächst sicher, dass Sie die Debugsymbole für Python (oder zumindest glibc) haben. Auf Fedora 1 können Sie sie installieren mit:

dnf debuginfo-install python

Hängen Sie dann gdb an das ausgeführte Skript an und führen Sie die folgenden Befehle aus:

[user@host ~]$ pidof python2
9219
[user@host ~]$ gdb python2 9219
GNU gdb (GDB) Fedora 7.7.1-13.fc20
...
0x00007fa934278780 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:81
81  T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
(gdb) call fflush(stdout)
$1 = 0
(gdb) call setvbuf(stdout, 0, 2, 0)
$2 = 0
(gdb) quit
A debugging session is active.

    Inferior 1 [process 9219] will be detached.

Quit anyway? (y or n) y
Detaching from program: /usr/bin/python2, process 9219

Dadurch wird stdout gelöscht und die Pufferung deaktiviert. Das 2aus dem setvbufAufruf ist der Wert von _IONBFauf meinem System. Sie müssen herausfinden, was auf Ihnen ist (ein grep _IONBF /usr/include/stdio.hsollte den Trick tun).

Basierend auf dem, was ich in der Implementierung von PyFile_SetBufSizeund PyFile_WriteStringin CPython 2.7 gesehen habe, sollte es ziemlich gut funktionieren, aber ich kann keine Garantien abgeben.


1 Fedora enthält einen speziellen RPM- Typ namens debuginfo rpms . Diese automatisch erstellten RPMs enthalten die Debugging-Informationen aus den Programmdateien, werden jedoch in eine externe Datei verschoben.

Cristian Ciupitu
quelle
Ich habe versucht, Python 2.7 und endete mit dem gleichen Ergebnis. Ich werde mir das Debugging-Update ansehen, das Sie gepostet haben.
DarkHeart
CPython 3.5 scheint eine andere Implementierung von I / O ( fileobject.c) zu haben als 2.7 . Jemand muss sich in das ioModul einarbeiten.
Cristian Ciupitu
@ DarkHeart, vielleicht möchten Sie zuerst mit einem einfachen Programm wie diesem testen .
Cristian Ciupitu
4

Es gibt keine Lösung für Ihr unmittelbares Problem. Wenn Ihr Skript bereits gestartet wurde, können Sie den Puffermodus nachträglich nicht mehr ändern. Dies sind alles In-Memory-Puffer, die beim Starten des Skripts, Öffnen von Datei-Handles, Erstellen von Pipes usw. eingerichtet werden.

Im Endeffekt können Sie nur dann einen syncBefehl ausführen, wenn die betreffende Pufferung teilweise oder vollständig auf der E / A-Ebene der Ausgabe erfolgt . In einem solchen Fall ist dies jedoch im Allgemeinen unwahrscheinlich.

In Zukunft können Sie Pythons -uOption * verwenden , um das Skript auszuführen. Im Allgemeinen verfügen viele Befehle über befehlsspezifische Optionen zum Deaktivieren der stdin / stdout-Pufferung, und Sie können auch allgemeine Erfolge mit dem unbufferBefehl aus dem expectPaket erzielen .

Ein Ctrl+ Cwürde dazu führen, dass Puffer auf Systemebene geleert werden, wenn das Programm unterbrochen wird, es sei denn, die Pufferung wird von Python selbst durchgeführt und es wurde die Logik zum Leeren der eigenen Puffer mit Ctrl+ nicht implementiert C. Ein Suspend, Crash oder Kill wäre nicht so nett.

* Erzwinge, dass stdin, stdout und stderr völlig ungepuffert sind.

Jason C
quelle
2

Python 2.7.7-Dokumentation, Abschnitt "Python-Setup und -Verwendung", Unterabschnitt 1. Befehlszeile und Umgebung , beschreibt dieses Python-Argument:

-u

Erzwinge, dass stdin, stdout und stderr völlig ungepuffert sind. Versetzen Sie auf Systemen, auf denen es darauf ankommt, auch stdin, stdout und stderr in den Binärmodus.

Beachten Sie, dass in file.readlines () und File Objects (für die Zeile in sys.stdin) eine interne Pufferung vorhanden ist, die von dieser Option nicht beeinflusst wird. Um dies zu umgehen, sollten Sie file.readline () in einer while 1: -Schleife verwenden.

Und auch diese Umgebungsvariable:

PYTHONUNBUFFERED

Wenn dies auf eine nicht leere Zeichenfolge festgelegt ist, entspricht dies der Angabe der Option -u.

Harrymc
quelle
1
Danke - aber diese beiden Optionen klingen wie Optionen, die ich angeben müsste, als ich mein Python-Skript zum ersten Mal ausgeführt habe. Ich frage mich, ob es eine Möglichkeit gibt, ein laufendes Skript dazu zu bringen, die Ausgabe zu sichern.
Josliber
Ich glaube nicht, dass es eine solche Lösung gibt, da sich die Daten wahrscheinlich irgendwo in einem Speicherpuffer befinden. Sie müssten eine DLL in Python einschleusen, die die ausführbare Datei gut genug kennt, um zu wissen, wo sich der Puffer befindet und wie man ihn ausschreibt. Ich glaube, die meisten Leute würden nur eine der beiden oben genannten Methoden anwenden. Schließlich ist das Hinzufügen einer Umgebungsvariablen recht einfach.
Harrymc
Gut zu wissen, dass es möglicherweise keine Lösung gibt. Wie in meiner Frage angegeben, weiß ich, wie man Puffer in Python spült (ich hätte es verwendet sys.stdout.flush(), aber Ihre -uOption scheint noch einfacher zu sein), hatte dies aber nur vergessen, als ich meinen Code aufrief. Nachdem ich meinen Code bereits länger als eine Woche ausgeführt hatte, hoffte ich, dass es eine Möglichkeit gab, meine Ausgabe zu erhalten, ohne den Code für eine weitere Woche erneut ausführen zu müssen.
josliber
Wenn Sie wissen, wie die Daten aussehen, können Sie mit Process Explorer einen vollständigen Speicherauszug des Prozesses erstellen und nach den Zeichenfolgen in der Datei suchen. Der Vorgang wird dadurch nicht beendet, sodass Sie noch andere Methoden ausprobieren können.
Harrymc
Ich bin unter Linux - gibt es Linux-Äquivalente dieser Software?
Josliber
2

Es scheint, dass ich übervorsichtig war, weil ich nach dem Ausführen von Strg-C durch gepufferte Ausgabe verloren habe. Nach diesem Post sollte ich erwarten, dass der Puffer geleert wird, wenn mein Programm einen normalen Exit hat, was der Fall wäre, wenn ich Strg-C drücke. Andererseits würde ich die gepufferte Ausgabe verlieren, wenn ich das Skript mit SIGKILL oder ähnlichem beenden würde.

josliber
quelle
Sie müssten es versuchen, um es herauszufinden. Ctrl-C bewirkt, dass Low-Level-E / A-Puffer geleert werden. Wenn Python selbst puffert, werden sie von Strg-C nur dann gelöscht, wenn Python die entsprechende Logik implementiert. Hoffentlich hat Python beschlossen, ein Rad nicht neu zu erfinden, und verlässt sich auf den normalen Pufferungsgrad des Systems. Ich habe keine Ahnung, ob das der Fall ist. Aber sei gewarnt.
Jason C
Das Betriebssystem kann niemals den Speicherplatz des Programms leeren. Was gelöscht wird, sind Daten im Systemspeicher, dh Daten, die bereits vom Programm mithilfe von Systemaufrufen geschrieben wurden. Im Fehlerfall werden auch diese Systempuffer verworfen. Kurz gesagt, Daten, die noch nicht von Python geschrieben wurden, können nicht gelöscht werden und gehen in jedem Fall verloren.
Harrymc
0

Ich denke, eine andere mögliche Lösung kann darin bestehen, den Prozessabbruch mit entleertem Kern zu erzwingen und dann den Speicherinhalt posthum zu analysieren.

Jacek
quelle