Ich habe Probleme damit, mein IRC-Bouncer / Benachrichtigungsskript zum Laufen zu bringen.
Dies ist ein Skript, das sich automatisch bei einem Remotecomputer anmeldet, eine Verbindung zu einer Bildschirmsitzung herstellt (oder eine startet, wenn derzeit keine vorhanden ist), in der weechat ausgeführt wird, und gleichzeitig eine andere SSH-Verbindung öffnet, die Netcat zum Lesen von Benachrichtigungen aus einer Socket-Datei verwendet In das ein Weechat-Add-On-Skript meine Benachrichtigungsnachrichten exportiert. Diese Benachrichtigungen werden dann in lib-notify (über notify-send) eingespeist, damit ich über Aktivitäten in weechat informiert werden kann.
Hier ist das Skript:
#!/bin/bash
BOUNCER="MYUSER@MYBOUNCER.NET"
function irc_notify() {
ssh $BOUNCER "nc -k -l -U /tmp/weechat.notify.sock" | \
while read type message; do
notify-send -i weechat -u critical "$(echo -n $type | base64 -di -)" "$(echo -n $message | base64 -di -)"
done
}
# Start listening for notifications
irc_notify &
# Attach to remote IRC Bouncer Screen
ssh $BOUNCER -t 'screen -d -R -S irc weechat'
# Cleanup Socket Listener
echo "cleaning up notification socket listener…"
ssh $BOUNCER 'pkill -f -x "nc -k -l -U /tmp/weechat.notify.sock"'
Das Setup funktioniert wirklich sehr gut, bis auf einen großen Fehler. Pro Aufruf des Skripts gelangten nur zwei Benachrichtigungen zu meinem Benachrichtigungsmanager. Danach nichts mehr.
Um Probleme mit dem Benachrichtigungsskript in weechat zu beseitigen, habe ich den zweiten ssh-Aufruf (der an die Bildschirmsitzung angehängt wird und weechat startet) entfernt und durch einen read
Befehl zum Blockieren der Ausführung während des Tests ersetzt. Dann habe irb
ich auf dem Remote-Computer mit Ruby Nachrichten an den Socket gesendet.
Aber selbst wenn ich die Nachrichten manuell zu senden, immer noch nur zwei Nachrichten erscheinen , bevor es aufgehört zu arbeiten .
strace
zeigte mir ein interessantes Verhalten (als ich mich an den gegabelten Prozess anhängte), bei dem es den Anschein hatte, dass die Nachrichten nach der ersten oder zweiten Nachricht nicht mehr durch Zeilenumbrüche beendet wurden. Aber nach ein paar mehr hörten sie auf, strace
zusammen zu erscheinen.
Zu diesem Zeitpunkt entschied ich mich zu prüfen, ob etwas in meinem Skript seltsames Verhalten verursachte. Also habe ich in der Kommandozeile einfach die ssh connection ( ssh $BOUNCER "nc -k -l -U /tmp/weechat.notify.sock"
) direkt aufgerufen . Und siehe da, alle Nachrichten, die ich manuell gesendet habe, wurden angezeigt (natürlich immer noch base64-codiert).
Also habe ich die Logik hinzugefügt, um jede eingehende Nachricht zu dekodieren, wie ich sie in meinem Skript hatte, und sie hat auch für jede Nachricht perfekt funktioniert. Einschließlich, wenn ich diese Nachrichten in Notify-Send eingespeist habe.
An diesem Punkt entschied ich, dass etwas Seltsames passieren muss, als ich die Funktion gabelte . Ich habe jedoch keinen Unterschied in der Effektivität festgestellt, als ich die Befehle im Terminal hinterlegt habe. Also fragte ich mich, ob vielleicht etwas Seltsames passierte, weil es aus einem Skript heraus ausgeführt wurde.
Dann wurde es komisch…
Ich begann damit, die Logik aus der Funktion herauszubrechen und sie direkt aufzurufen, mit einem kaufmännischen Und am Ende der Pipe-Befehle. Wie so:
ssh $BOUNCER "nc -k -l -U /tmp/weechat.notify.sock" | \
while read type message; do
notify-send -i weechat -u critical "$(echo -n $type | base64 -di -)" "$(echo -n $message | base64 -di -)"
done &
Sobald ich das tat, begannen die Nachrichten plötzlich zu funktionieren. Und sobald ich die Änderung rückgängig gemacht hatte, war ich mit demselben seltsamen Verhalten, das nur aus zwei Nachrichten bestand, wieder auf dem ersten Platz.
Aber dieses Update führte zu einem anderen seltsamen Verhalten. Sobald ich mich in der Bildschirmsitzung befand, musste ich jede Taste mehrmals drücken, bevor sie vom Programm registriert wurde. Als ob es eine Rassenbedingung gäbe, die um STDIN kämpft.
Als ich herausfand, dass sich die beiden SSH-Sitzungen möglicherweise darum stritten (obwohl ich nicht sicher war, warum), versuchte ich, STDIN beim ersten ssh-Befehl mit verschiedenen Mitteln zu schließen und / oder zu besetzen. : |
Zum Beispiel durch Einleiten vor <&-
oder </dev/null
nach dem Anbringen oder nach dem SSH-Teil des Rohrs. Und während dies die Rassenbedingung zu lösen schien, führte dies das Zwei-Nachrichten-Verhalten wieder ein.
Da ich dachte, dass es etwas damit zu tun haben könnte, dass ich mich auf mehreren Ebenen der Unterverarbeitung befinde , versuchte ich, dies zu reproduzieren, indem ich den SSH-Aufruf bash -c
wie folgt umschloss : bash -c 'ssh $BOUNCER "nc -k -l -U /tmp/weechat.notify.sock" &'
. Und auch dies zeigte das Zwei-Nachrichten-Verhalten.
Ich habe dies auch direkt auf dem Remote-Computer getestet (SSHing zu localhost und Einschließen von zwei bash -c
Aufrufen) und das gleiche fehlerhafte Verhalten festgestellt . Es scheint auch nicht mit Doppelgabeln zu tun zu haben, die verwaiste Prozesse verursachen. Da es keine Rolle zu spielen scheint, ob der Prozess verwaist ist oder nicht.
Ich habe auch überprüft, dass dies auch unter geschiehtzsh
.
Es scheint, dass dies irgendwie mit der Art und Weise zusammenhängt, wie STDIN und STDOUT behandelt werden, wenn ein Prozess unter Ebenen der Unterverarbeitung ausgeführt wird.
Repro. Anleitung & strace
Ausgabe:
Um das Debuggen zu vereinfachen, habe ich SSH aus dem Bild entfernt und zwei vereinfachte Testskripte geschrieben, die das Verhalten vollständig lokal erfolgreich reproduzierten.
Mit dem socket
Befehl von Jürgen Nickelsen habe ich einen lokalen UNIX Domain Socket ( socket -l -s ./test.sock
) erstellt und konnte erneut Testnachrichten mit irb
dem folgenden Ruby-Code senden :
require 'socket'
require 'base64'
SOCKET = './test.sock'
def send(subtitle, message)
UNIXSocket.open(SOCKET) do |socket|
socket.puts "#{Base64.strict_encode64(subtitle)} #{Base64.strict_encode64(message)}"
end
end
send('test', 'hi')
send('test', 'hi')
send('test', 'hi')
send('test', 'hi')
send('test', 'hi')
send('test', 'hi')
Das erste Skript hat nur den Pipeline-Ausdruck hinterlegt (der, wie bereits erwähnt, eine unbegrenzte Anzahl von Nachrichten verarbeitet hat):
#!/bin/bash
# to aid in cleanup when using Ctrl-C to exit strace
trap "pkill -f -x 'nc -k -l -U $HOME/test.sock'; exit" SIGINT
# Start listening for notifications
nc -k -l -U $HOME/test.sock | \
while read type message; do
# write messages to a local file instead of sending to notification daemon for simplicity.
echo "$(echo -n $type | base64 -di -)" "$(echo -n $message | base64 -di -)" >> /tmp/msg
done &
read
Beim Ausführen mit wurde die folgende Ausgabe erstellt strace -f
: http://pastebin.com/SMjti3qW
Das zweite Skript hat die Wrapping-Funktion (die das 2-and-done-Verhalten auslöst) im Hintergrund ausgeführt:
#!/bin/bash
# to aid in cleanup when using Ctrl-C to exit strace
trap "pkill -f -x 'nc -k -l -U $HOME/test.sock'; exit" SIGINT
# Start listening for notifications
function irc_notify() {
nc -k -l -U $HOME/test.sock | \
while read type message; do
# write messages to a local file instead of sending to notification daemon for simplicity.
echo "$(echo -n $type | base64 -di -)" "$(echo -n $message | base64 -di -)" >> /tmp/msg
done
}
irc_notify &
read
Und was wiederum die folgende Ausgabe erzeugte, wenn es ausgeführt wurde mit strace -f
: http://pastebin.com/WsrXX0EJ
Eine Sache, die mir beim Betrachten der strace
Ausgabe der obigen Skripte auffällt, ist die für den nc
Befehl spezifische Ausgabe . Dies scheint einen der Hauptunterschiede zwischen der Ausführung dieser beiden Skripte aufzuzeigen.
Die "funktionierende" nc
strace
Ausgabe des ersten Skripts :
accept(3, {sa_family=AF_FILE, NULL}, [2]) = 4
poll([{fd=4, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 1 ([{fd=4, revents=POLLIN|POLLHUP}])
read(4, "dGVzdA== aGk=\n", 2048) = 14
write(1, "dGVzdA== aGk=\n", 14) = 14
poll([{fd=4, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 1 ([{fd=4, revents=POLLIN|POLLHUP}])
read(4, "", 2048) = 0
shutdown(4, 0 /* receive */) = 0
close(4) = 0
accept(3, {sa_family=AF_FILE, NULL}, [2]) = 4
poll([{fd=4, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 1 ([{fd=4, revents=POLLIN|POLLHUP}])
read(4, "dGVzdA== aGk=\n", 2048) = 14
write(1, "dGVzdA== aGk=\n", 14) = 14
poll([{fd=4, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 1 ([{fd=4, revents=POLLIN|POLLHUP}])
read(4, "", 2048) = 0
shutdown(4, 0 /* receive */) = 0
close(4) = 0
accept(3, {sa_family=AF_FILE, NULL}, [2]) = 4
poll([{fd=4, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 1 ([{fd=4, revents=POLLIN|POLLHUP}])
read(4, "dGVzdA== aGk=\n", 2048) = 14
write(1, "dGVzdA== aGk=\n", 14) = 14
poll([{fd=4, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 1 ([{fd=4, revents=POLLIN|POLLHUP}])
read(4, "", 2048) = 0
shutdown(4, 0 /* receive */) = 0
close(4) = 0
accept(3, {sa_family=AF_FILE, NULL}, [2]) = 4
poll([{fd=4, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 1 ([{fd=4, revents=POLLIN|POLLHUP}])
read(4, "dGVzdA== aGk=\n", 2048) = 14
write(1, "dGVzdA== aGk=\n", 14) = 14
poll([{fd=4, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 1 ([{fd=4, revents=POLLIN|POLLHUP}])
read(4, "", 2048) = 0
shutdown(4, 0 /* receive */) = 0
close(4) = 0
accept(3, {sa_family=AF_FILE, NULL}, [2]) = 4
poll([{fd=4, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 1 ([{fd=4, revents=POLLIN|POLLHUP}])
read(4, "dGVzdA== aGk=\n", 2048) = 14
write(1, "dGVzdA== aGk=\n", 14) = 14
poll([{fd=4, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 1 ([{fd=4, revents=POLLIN|POLLHUP}])
read(4, "", 2048) = 0
shutdown(4, 0 /* receive */) = 0
close(4) = 0
accept(3, {sa_family=AF_FILE, NULL}, [2]) = 4
poll([{fd=4, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 1 ([{fd=4, revents=POLLIN|POLLHUP}])
read(4, "dGVzdA== aGk=\n", 2048) = 14
write(1, "dGVzdA== aGk=\n", 14) = 14
poll([{fd=4, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 1 ([{fd=4, revents=POLLIN|POLLHUP}])
read(4, "", 2048) = 0
shutdown(4, 0 /* receive */) = 0
close(4) = 0
accept(3,
Das "2-and-done" -Verhalten des zweiten Skripts in der nc
strace
Ausgabe:
accept(3, {sa_family=AF_FILE, NULL}, [2]) = 4
poll([{fd=4, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 2 ([{fd=4, revents=POLLIN|POLLHUP}, {fd=0, revents=POLLHUP}])
read(4, "dGVzdA== aGk=\n", 2048) = 14
write(1, "dGVzdA== aGk=\n", 14) = 14
shutdown(4, 1 /* send */) = 0
close(0) = 0
poll([{fd=4, events=POLLIN}, {fd=-1}], 2, -1) = 1 ([{fd=4, revents=POLLIN|POLLHUP}])
read(4, "", 2048) = 0
shutdown(4, 0 /* receive */) = 0
close(4) = 0
accept(3, {sa_family=AF_FILE, NULL}, [2]) = 0
poll([{fd=0, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 2 ([{fd=0, revents=POLLIN|POLLHUP}, {fd=0, revents=POLLIN|POLLHUP}])
read(0, "dGVzdA== aGk=\n", 2048) = 14
write(1, "dGVzdA== aGk=\n", 14) = 14
read(0, "", 2048) = 0
shutdown(0, 1 /* send */) = 0
close(0) = 0
poll([{fd=0, events=POLLIN}, {fd=-1}], 2, -1) = 1 ([{fd=0, revents=POLLNVAL}])
poll([{fd=0, events=POLLIN}, {fd=-1}], 2, -1) = 1 ([{fd=0, revents=POLLNVAL}])
poll([{fd=0, events=POLLIN}, {fd=-1}], 2, -1) = 1 ([{fd=0, revents=POLLNVAL}])
poll([{fd=0, events=POLLIN}, {fd=-1}], 2, -1) = 1 ([{fd=0, revents=POLLNVAL}])
poll([{fd=0, events=POLLIN}, {fd=-1}], 2, -1) = 1 ([{fd=0, revents=POLLNVAL}])
poll([{fd=0, events=POLLIN}, {fd=-1}], 2, -1) = 1 ([{fd=0, revents=POLLNVAL}])
poll([{fd=0, events=POLLIN}, {fd=-1}], 2, -1) = 1 ([{fd=0, revents=POLLNVAL}])
poll([{fd=0, events=POLLIN}, {fd=-1}], 2, -1) = 1 ([{fd=0, revents=POLLNVAL}])
poll([{fd=0, events=POLLIN}, {fd=-1}], 2, -1) = 1 ([{fd=0, revents=POLLNVAL}])
poll([{fd=0, events=POLLIN}, {fd=-1}], 2, -1) = 1 ([{fd=0, revents=POLLNVAL}])
.......[truncated].......
Ich bin nicht dort, wo ich in Bezug auf meine strace
Lesbarkeit der Ausgabe sein möchte , daher bin ich mir nicht ganz sicher, was diese verschiedenen Ausgaben bedeuten - abgesehen von der Tatsache, dass eine offensichtlich funktioniert, während die andere nicht funktioniert.
Während ich die größere strace
Ausgabe durchgearbeitet habe , scheint es auch, dass Nachrichten nach den ersten beiden nicht mehr durch einen Zeilenumbruch beendet werden? Aber auch hier bin ich mir nicht sicher, was das bedeutet oder ob ich es richtig lese.
Und ich verstehe definitiv nicht, wie die verschiedenen Subverarbeitungstechniken oder sogar das Schließen von STDIN dieses Verhalten beeinflussen könnten.
Irgendeine Idee, was mir hier begegnet?
- -
tl; dr
Ich versuche herauszufinden, warum das Ausführen meines Benachrichtigungslisteners unter mehr als einer Unterverarbeitungsebene dazu führt, dass nur zwei Nachrichten verarbeitet werden. Andernfalls führt dies zu einer Rennbedingung für STDIN.
-n
zu ssh scheint den gleichen Effekt zu haben wie jeder andere Versuch, STDIN zu schließen (zwei und erledigtes Verhalten). Ebenso wie das Schließen von STDIN im Unterprozess. Ich lief jedoch strace
weiter nc
und es zeigt einige interessante Verhaltensunterschiede zwischen Szenarien, in denen alle Nachrichten durchkommen ( pastebin.com/tCSwtA99 ) und wenn nur zwei durchkommen ( pastebin.com/gZueVpb0 ). Ich bin nicht der Beste im Lesen von strace
Ausgaben. Möglicherweise muss ich einige Systemaufrufdokumentationen durchgehen, um dies zu verstehen.
strace -f
? Darauf folgen Gabeln, die Spuren aller erzeugten Unterprozesse enthalten.
-f
zum nc
strace
hat keine Auswirkung, da es nc
überhaupt nicht gabelt. Ich würde Ihnen die vollständige Ausgabe des Ausführens auf dem clientseitigen Skript geben, aber das, was dies bietet, ist so lang und enthält so viele vertrauliche Informationen (von LDAP usw.), dass ich nicht richtig bereinigen kann, um sie hier einzufügen. Aber ich kann Ihnen sagen, was ich immer beobachte, wenn ich dies versuche, dass immer zwei Nachrichten weitergeleitet werden, aber die letzte scheint nicht richtig beendet zu sein (es fehlt ein Zeilenumbruchzeichen) oder so, und das scheint zu sein fallen mit dem Zeitpunkt zusammen nc
, an dem poll()
Nachrichten gesendet werden.
strace
Ausgabe führte. Ich habe zwei Versionen dieses Testskripts mit ausgeführt strace -f
. eine, die den Piped-Ausdruck hinterlegt und unbegrenzt Testnachrichten verarbeitet ( pastebin.com/GDjxTJuJ ), und eine, die eine Umbruchfunktion hinterlegt und nur zwei Nachrichten verarbeitet, bevor sie unterbrochen wird ( pastebin.com/GMtkKefP ). Der strace -f
Ausgang dieser jeweiligen Skripte war 1) pastebin.com/SMjti3qW und 2) pastebin.com/WsrXX0EJ
ssh -n
das Lesen von stdin verhindert, obwohl dies nicht die Ursache für das Hauptproblem zu sein scheint. Was zeigtstrace
dernc
Prozess im Vergleich zum Unterprozess? Istnc
Empfangen und Emittieren Daten? Blockiert es? Hat das Schließen von stdout für den Unterprozess Auswirkungen?