Fabric: Lokale Terminal-Stdin wurde getrennt, wenn ThreadingGroup-Ausführung Schlaf enthält

Erstellt am 25. Juni 2018  ·  22Kommentare  ·  Quelle: fabric/fabric

Ich verwende eine Threading-Gruppe, um Shell-Befehle auszuführen. Nachdem ein Skript ausgeführt wurde, das ein sleep , bleibt das lokale Terminal mit stdin getrennt (Tastenanschläge in der Befehlszeile nicht sichtbar) und das Terminal muss zurückgesetzt werden.

Ich habe dies einige Male versucht und festgestellt, dass dies nur mit ThreadingGroups passiert (SerialGroups sind in Ordnung). Der sleep-Befehl kann an einer beliebigen Stelle in einem Einzeiler stehen (erster Befehl, mittlerer, letzter) und kann entweder mit Semikolon oder doppeltem kaufmännischem Und-Zeichen in einem Zeilenumbruch verbunden werden. Alle Befehle werden wie erwartet ausgeführt, aber das Terminal bleibt in einem schlechten Zustand.

Seltsamerweise ist das Terminal nicht betroffen, wenn der vorherige Lauf mit einer nicht abgefangenen Ausnahme beendet wurde.

Fortpflanzen:

from fabric import ThreadingGroup as Group

# raise ValueError()
remotes = Group("host1.example.com", "host2.example.com")
result = remotes.run("echo 1; sleep 1; echo 2")

Führen Sie das obige Skript aus. Geben Sie nach dem Beenden etwas in die Befehlszeile ein. Wenn Sie keine Ausgabe sehen, geben Sie <ctrl>+c und geben Sie reset<enter> . Um das Verhalten nach der Ausnahme anzuzeigen, entkommentieren Sie die Zeile raise , führen Sie den Code aus, kommentieren Sie die Zeile und führen Sie sie noch zweimal aus. Der erste erfolgreiche Lauf verlässt das Terminal in einem guten Zustand. Das zweite lässt stdin los.

Ich habe dieses Problem bei meinen Tests mit sleep , aber es ist möglich, dass andere Befehle den gleichen Effekt haben. Es besteht auch die Möglichkeit, dass ich einfach etwas falsch mache. Wenn das der Fall ist, bitte ich um Entschuldigung.

Mein Setup:
Python 3.6.4
Stoff 2.1.3
OSX 10.13.5, Verbindung zu Ubuntu 14.04

Bug Needs investigation

Alle 22 Kommentare

Siehe #1814 als möglichen zweiten reproduzierbaren Problemfall.

Das hört sich für mich nach einem legitimen Fehler an und ich bin mir nicht sicher, woran es liegt. Es riecht, als könnte es sich um ein allgemeines Unix-Problem handeln, bei dem Terminal-Pipes gleichzeitig an mehrere Unterprozesse angehängt werden, oder (insbesondere nach dem Beispiel von # 1814) eine Race-Bedingung um den Pipe-Status oder ähnliches.

Werde versuchen, eine Ursache/Lösung zu reproduzieren und durcheinander zu bringen.

Außerdem erfordert dies wahrscheinlich eine Korrektur auf Invoke-Ebene und kann rein in seiner Domäne sein (insofern ich noch nicht viel mit Threading in einem reinen Invoke-Kontext gemacht habe; aber siehe z. B. pyinvoke/invoke#194 - es ist eine Sache, die sollte dort auch passieren). In diesem Fall verschiebe ich dies in ein Ticket dort und der Fabric-"Fix" wäre, seinen Invoke zu aktualisieren, sobald der Fix veröffentlicht wurde.

Ich war auf Ubuntu 16.04.2 und habe mich mit demselben verbunden.

Ein weiterer Bericht über das gleiche Problem in #1829. Dies ist in meinem nächsten Meilenstein für die Fehlerbehebung und ich werde mich darauf konzentrieren, hoffentlich am nächsten OSS-Tag (Mo).

Ich habe gerade versucht, dies zu reproduzieren (2.0-Zweig, Python 3.6.4, macOS 10.12) und konnte es leider nicht. Zuerst versuchten es Double-Localhost, dann zwei separate Remote-Cloud-Instanzen, kein Würfel so oder so; Mein Terminal ist danach in Ordnung.

Ich werde in Kürze einen Linux-Container ausprobieren, falls das hilft, aber da das OP auch auf macOS war, ist es nicht sicher, ob es einen Unterschied macht. Ich werde auch versuchen, es in einer Schleife auszuführen, um zu sehen, ob es sich nur um eine gelegentliche Reproduktion handelt.

Ich werde es auch mit 2.1 versuchen, falls wir es irgendwie in 2.1 eingeführt haben, obwohl dies sehr unwahrscheinlich erscheint.

@jensenak @nicktimko reproduzierst du das zu 100%? 50%? 5%?

@bitprophet auf 2.1.3 passierte es in meinem tatsächlichen Workflow ziemlich oft (> 80%, ich ging auch parallel zu 6 Servern, nicht 2), obwohl es in meinem erfundenen Beispiel von # 1814 viel niedriger ist, vielleicht 20%. Ich kann versuchen, ein Docker-Setup zu erstellen oder ein Vagrant-Setup zum Repro zu erstellen.

@bitprophet Dies war für mich 100% der Zeit. Nur um sicher zu gehen, habe ich eine neue virtuelle Umgebung gestartet, bei der nur Fabric installiert ist. Getestet habe ich 2.0, 2.1 und 2.2. Der von mir eingefügte Beispielcode erzeugte jedes Mal das beschriebene Verhalten. In allen Tests habe ich eine Verbindung zu Ubuntu 14.04-Fernbedienungen hergestellt.

Ich verwende eine andere Version von OSX (10.13). Vielleicht hängt das zusammen? Obwohl @nicktimko überhaupt nicht auf OSX war.

Falls eine andere Version ein Problem darstellt, sah pip freeze in meiner virtuellen Umgebung folgendermaßen aus:

asn1crypto==0.24.0
bcrypt==3.1.4
cffi==1.11.5
cryptography==2.3
fabric==2.2.1
idna==2.7
invoke==1.1.0
paramiko==2.4.1
pyasn1==0.4.4
pycparser==2.18
PyNaCl==1.2.1
six==1.11.0

Da all dies als Abhängigkeiten von Fabric 2.2 installiert wurde, würde ich erwarten, dass Ihre Versionen ähnlich aussehen werden.

Wenn ich mehr tun kann, um zu helfen, bin ich mehr als bereit. Ich bin mir nur nicht sicher, wo ich noch suchen soll.

Mit welchem ​​Commit soll ich testen; Haben Sie in letzter Zeit Änderungen vorgenommen, die sich auf die Dinge auswirken könnten? Ich werde es mit dem obigen Einfrieren versuchen, Sie könnten auch ein weiteres eingefrorenes reqs.txt bereitstellen und ich kann sehen, ob das für mich funktioniert / nicht.

@nicktimko @jensenak Danke für die zusätzlichen Infos. Ich werde weiter versuchen, es hier zu reproduzieren; bei 20% hätte ich es definitiv nicht genug probiert um auszulösen. Meine Fernbedienungen waren Mac und einige ältere Debians, ich kann Ubuntu Trusty ausprobieren, falls es irgendwie spezifisch dafür ist (was seltsam wäre, aber hey, das Ganze ist seltsam.)

Und was ist Ihre lokale Shell-Umgebung? Meins ist zsh auf der (wieder macOS 10.12) integrierten Terminal.app in tmux. Ich werde auch einige Permutationen um diesen Winkel herum ausprobieren.

AHA. Das scheint Bash-spezifisch zu sein! Konnte immer noch nicht unter zsh außerhalb von tmux reproduzieren, aber sobald ich es unter bash versuche, bekomme ich sofort die genannten Symptome. Dito innerhalb von tmux, also hat tmux keine Bedeutung - es ist eine Shell-Sache.

_Warum_ sich das unter bash und zsh anders verhalten würde, habe ich keine Ahnung. Könnte spezifisch sein, wie sie implementiert werden, oder (scheint wahrscheinlicher) möglicherweise etwas in meinen ZSH-Punktdateien das Problem verhindert? Muss graben ... obwohl die Identifizierung einer Lösung auf der Python-Seite höchstwahrscheinlich notwendig ist.

BEARBEITEN: Die Reproduktion erfolgt auch, wenn ich mich mehrmals gleichzeitig mit dem sshd meines localhosts verbinde, was nicht allzu überraschend ist. Das entfernte Ende scheint also keine Rolle zu spielen.

Außerdem habe ich versucht, den Hinweis "Vorherige Ausführung außer verhindert Problem nur für die nächste Ausführung zu überprüfen" zu überprüfen, aber das ist bei mir nicht aufgetreten; Ich bekomme das Verhalten jedes Mal unabhängig davon.

Moar: Ich habe sleep zu sehen, was passieren würde; Ich kann immer noch reproduzieren, obwohl es jetzt etwas intermittierender ist (obwohl dies in einer automatischen Schleife nicht einfach zu reproduzieren ist, erfolgt die Reproduktion von Hand, was eine geringe Anzahl von Testfällen bedeutet, was bedeutet, dass der wahre Prozentsatz des Auftretens real sein wird schwer genau zu messen.)

Das ist auch gut so, je weniger seltsame Auslöser, desto besser. Das riecht so, als ob es irgendwo ein einfacher, dummer Threading-Fehler sein sollte, der normalerweise nicht durch irgendetwas Spezifisches am entfernten oder lokalen Ende beeinflusst wird, abgesehen von der Zeitdauer, die eine Race-Condition (oder w/e) wahrscheinlicher macht.

Ich frage mich, ob dies mit pyinvoke/invoke#552 zusammenhängt, was darauf zurückzuführen ist, dass Invokes Thread-Unterklasse zur Ausnahmebehandlung (die hier in ThreadingGroup verwendet wird) möglicherweise die Thread-Death-Erkennung vermasselt hat.

Ich muss sicherstellen, dass ich das verstehe (die potenzielle Lösung, pyinvoke/invoke#553, war keine Insta-Zusammenführung, da es seltsam schien, dass wir etwas anscheinend Funktionelles, also Falsches bekommen hätten) und dann sehen, ob die Anwendung es macht dieses Symptom verschwindet.

Ich entfernte den Schlaf, um zu sehen, was passieren würde; Ich kann mich immer noch reproduzieren, obwohl es jetzt etwas intermittierender ist

Klingt wie der Testfall, den ich hatte, wo ich ihn ein paar Mal treffen musste, bevor er abgehört hat. Du scheinst das gut im Griff zu haben

Mir ist heute aufgefallen, dass ich auch das von mir vor einem Monat beschriebene Exception-Verhalten nicht reproduzieren konnte... leider weiß ich nicht mehr, was ich damals gemacht habe. :/

Ich lasse hier tatsächlich Bash laufen. Guter Fund. Die Tatsache, dass das Problem ohne den Schlaf zeitweise auftritt, lässt mich fragen, ob dies eine Art Race Condition ist.

Sie sagen das, aber jetzt kann ich es nicht noch einmal reproduzieren, oder zumindest ist es SEHR zeitweilig. Wenn man den Schlaf wieder einsetzt, kommt er viel häufiger vor. Ich muss die Rennbedingungen lieben.

Wenn man sich dieses Invoke-Problem ansieht, erwähnt der Reporter sogar ein kaputtes Terminal als Symptom; aber seltsamerweise kann ich _dieses_ Symptom selbst unter Bash mit seinem Code nicht reproduzieren. Es würde mich jedoch nicht wundern, wenn die Ursache dieselbe ist (hat mit ein paar Dingen zu tun, die mit dem Tod von Threads und dem Schließen von stdin oder dem Zurücksetzen auf zeilenweise Pufferung vor dem Beenden zu tun haben).

Überprüfen Sie die Spots, die das andere Problem erwähnt hat, mit dem Repro-Fall hier:

  • das ExceptionHandlingThread.is_dead Bit scheint keine Rolle zu spielen, es wird vermutlich korrekt angezeigt, was einen Sinn ergibt, da es Ausnahmen im Thread behandeln soll und keiner dieser Fälle Ausnahmen behandelt. is_dead ist False für alle 3 Arbeitsthreads (stdin/out/err), wenn ich es erwarten würde.
  • die Behauptung, dass wir den stdin des Unterprozesses nicht richtig schließen, kommt dem Ziel näher; wenn das stdin des steuernden Terminals an einen jetzt toten Dateideskriptor angehängt bleibt oder so ...? (Ich sollte sowieso besser wissen, was in diesem Fall passiert.)

    • Außer ... im Fall von Fabric gibt es keinen lokalen Unterprozess und kein direktes Passthrough von Dateideskriptoren, so dass dies nicht der Fall sein kann.

    • Bedeutet das, dass das Problem eher etwas anderes ist?


Versuchen Sie einen anderen Weg ... was genau hat sich an der Terminalumgebung geändert, nachdem der Fehler aufgetreten ist? Wenn ich stty -a unter bash sowohl mit als auch ohne vorhandene Fehlerbeschädigung ausführe, sehe ich folgende Unterschiede:

  • lflags : Das fehlerhafte Terminal hat -icanon , -echo , -pendin (im Gegensatz zu regulären Ausdrücken, bei denen alle kein Minuszeichen haben). Kein Echo scheint sicherlich ein Problem zu sein, vorausgesetzt, dies bedeutet.
  • iflags : bugged-out hat -ixany und ignpar (das erste Beispiel dafür, dass etwas im schlechten Setup gesetzt, nicht ungesetzt ist)
  • oflags und cflags identisch, ebenso wie cchars (Ich wäre echt sauer, wenn sich die Steuerzeichen geändert hätten...)

Laut man stty :

  • icanon steuert die ERASE- und KILL-Verarbeitung; wahrscheinlich kein großer Unterschied (obwohl dies gesetzt oder nicht gesetzt ist, könnte interessant sein)
  • echo ist das, wonach es sich anhört, ob es ein Echo sein soll, und ist eindeutig das größte praktische Problem des Fehlers.
  • pendin gibt an, ob eine Eingabe (vorausgesetzt stdin) nach einem kanonischen Wechsel ansteht (und da icanon eindeutig umgedreht ist...ja) und erneut eingegeben wird, wenn ein Lesevorgang aussteht oder mehr Eingaben kommt an. Nicht klar, warum dies wichtig ist oder warum es normal gesetzt und bei Fehlern nicht gesetzt ist (ich hätte letzteres erwartet, wenn überhaupt.)
  • ixany erlaubt jedem Zeichen, die Ausgabe zu starten (und wenn nicht gesetzt, erlaubt nur START. ok?)
  • ignpar bedeutet, Zeichen mit Paritätsfehlern zu ignorieren (oder zu deaktivieren, nicht zu ignorieren).

Alles in allem fühlt es sich an, als ob ein höherer 'Modus' auf das Terminal angewendet wird, ähnlich wie wir stdin auf zeichengepuffertes Lesen setzen, damit wir jeweils 1 Byte lesen können, anstatt zu warten, bis der Benutzer masht.

Was sich wie das angezeigte Verhalten anhört (irgendwie...), und über das ich mich früher gewundert habe; aber wenn man den fraglichen Code liest (weil der Invoke-Patch ihn auch erwähnt, allerdings in Bezug auf Thread-Tod), ist die Modusänderung als Kontextmanager formuliert, also _sollte_ er immer wieder aufgehoben werden, unabhängig davon, wie wir aus dieser Schleife ausbrechen. Aber das muss ich jetzt dreifach überprüfen.

Minor: einfach zu sagen , stty echo einstellen echo ist ausreichend , um 'fix' ein Terminal; auch wenn icanon , pendin usw. noch nicht gesetzt sind. Hilft nicht wirklich, aber hey, gut zu wissen, denke ich.

OK! Ich glaube, ich habe es herausgefunden, während ich auf diesen Kontextmanager starrte: Es liegt wahrscheinlich daran, dass der Kontextmanager den aktuellen Terminalzustand zur Wiederherstellung am Ende des Blocks aufnimmt. Aber was machen wir in diesem Fall? Wir führen _zwei separate High-Level-Threads_ aus, von denen jeder seine _eigene Kopie_ dieses Kontext-Managers ausführt!

Und während wir in Invoke threadsicher sein wollen, testen wir derzeit nichts anderes als unsere eigenen Low-Level-IO-Threads; 99% der "Thread-Sicherheit" ist einfach die Verwendung eines in sich geschlossenen Objektstatus anstelle des schrecklichen globalen Modulstatus von Fabric 1 . Daher wird dieses spezielle Stück der Zustandswahrung niemals gleichzeitig mit sich selbst ausgeführt (zum Teil, weil der "Staat" buchstäblich das kontrollierende Terminal ist, von dem es immer nur eines gibt, also ... globaler Zustand ...).

Ich habe es noch nicht zu 100% bewiesen (in Kürze), aber das ist es auf keinen Fall. Der zweite Thread wird höchstwahrscheinlich die steuernden Terminalattribute abspeichern, _nachdem_ der erste Thread ihn bereits in den Zeichenpuffermodus versetzt hat; dann, wenn dieser zweite Thread auch den zweiten _beendet_ (wieder wahrscheinlich, aber nicht sicher), "wiederherstellt" er den schlechten Zustand, wodurch die Wiederherstellung des ersten Threads effektiv rückgängig gemacht wird.

Bestätigt, dass zum Beispiel das ECHO-Flag definitiv vom nicht-ersten Kontextmanager erfasst und dann von diesem wiederhergestellt wird. Die Arbeit an einer Lösung, von der ich denke, dass sie nur "versuchen wird, herauszufinden, ob setcbreak bereits angewendet aussieht, und in diesem Fall kein Op, anstatt den Snapshot-Modify-Restore-Tanz zu machen".

Sollte die beabsichtigte Wirkung haben, ist beim Booten geringfügig sauberer (führt setcbreak nie > 1 Mal aus) und vermeidet einen Eckfall, in dem ein naiver Fix ECHO usw tty-ähnlich, war aber _already_ so eingestellt, dass es nicht widerhallt. (Unwahrscheinlich, sicher, aber wahrscheinlich nicht unmöglich.)

Da dies nur ein Invoke-Problem ist, werde ich es auf diesem Tracker zu Hause geben - ich erwarte, dass bald ein Test und eine Fehlerbehebung durchgeführt werden, aber wenn Sie noch etwas hinzufügen möchten, gehen Sie bitte zu https ://github.com/pyinvoke/invoke/issues/559

Um es klar zu sagen, sobald das behoben ist, sollte es in Invoke 1.0.2/1.1.1 (und möglicherweise 1.2.0, wenn ich das gleichzeitig herausbekomme) verfügbar sein und _keine_ Fabric-Upgrades sollten notwendig sein, nur Invoke.

@bitprophet Großartig! Es funktioniert nach dem Upgrade von Invoke :)
Vielen Dank für Ihre Mühe.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen