Kapitel 8
Langsame Clients und verlorene Verbindungen
Ein langsamer Zuschauer bremst alle
Anzeigetafel v1 funktioniert, und sie hat eine Schwäche, die du in scoreboard_server.py ablesen kannst. apply_score nimmt das Lock der Tafel, erhöht die Version und ruft broadcast auf, das den neuen Snapshot an jeden Zuschauer sendet, einen nach dem anderen, bevor das Lock freigegeben wird. Erst wenn apply_score zurückgekehrt ist, sendet scorekeeper_handler seine Antwort. Jede Antwort an den Punktezähler wartet also, bis jedem Zuschauer das Update gesendet wurde. Diese Lektion fragt, was passiert, wenn einer dieser Sendevorgänge langsam ist.
Wenn ein Senden wartet
Die Antwort kennst du aus Networks with Python: sendall kehrt zurück, sobald das send der Bibliothek arbeitet darunter genauso. Solange die Gegenseite weiterliest, leeren sich die Puffer, und nichts wartet. Wenn eine Gegenseite aufhört zu lesen, füllt sich ihr Empfangspuffer, dann füllt sich dahinter dein Sendepuffer, und erst dann hat ein Senden keinen Platz mehr für die nächsten Bytes. Ab diesem Moment wartet das Senden, bis die Gegenseite wieder liest. Dass ein Empfänger, der nicht mithalten kann, seinen Sender auf diese Weise bremst, nennt man Backpressure (Gegendruck).
Das Programm eines Zuschauers hört auf zu lesen. Wann beginnt das send des Servers an ihn zu warten?
Probier es mit einem echten Client aus
Zu den Projektdateien gehört pl_ws.py, ein für diesen Kurs geschriebenes Programm, das jeden Client spielt und das du im letzten Kapitel ausgeführt hast. Sein Szenario stalled-reader startet deinen Server, verbindet Zuschauer
python pl_ws.py stalled-reader
Ein Lauf auf der Seite hat ausgegeben:
scenario stalled-reader: viewer R completes its handshake and then never reads again
server options: the library's defaults with close_timeout=1.0 (open_server is not written yet)
viewer R is registered and reads nothing from now on
the scorekeeper got 10 replies out of 10
the server finished 11 sends to R, which read none of them; R was still registered: True
the kernel's buffers between the server and R took every one of those snapshots
the server shut down
Elf Sendevorgänge, der Beitritts-Snapshot und zehn Updates, und kein einziger hat gewartet. Die zweite Zeile erwähnt open_server, das Lektion 4 schreibt; bis dahin verwendet das Programm die eigenen Einstellungen der Bibliothek. Auf einem lokalen Computer hat eine send schließlich wartete.
Warum bremst slow-viewer einen Zuschauer absichtlich, statt einen echten Client zu verwenden, der aufhört zu lesen?
Ein absichtlich gebremster Zuschauer
Im Internet füllt ein Zuschauer mit einer schwachen Handyverbindung diese Puffer schnell, und seine Sendevorgänge dauern dann so lange, wie sein Netzwerk braucht. Um diese Kosten auf einer Maschine zu zeigen, verbindet das Szenario slow-viewer Zuschauer A und Zuschauer S, einen Zuschauer, den dieses Programm absichtlich bremst: Jedes Senden von deinem Server an S wartet 0,3 s, bevor es hinausgeht. Dann sendet der Punktezähler drei Wertungen.
python pl_ws.py slow-viewer
Derselbe Lauf auf der Seite hat ausgegeben:
scenario slow-viewer: viewer S is slowed down on purpose; every send from the server to S waits 0.3 s first
server options: the library's defaults with close_timeout=1.0 (open_server is not written yet)
viewer A joined; its first message was a snapshot with version 0
viewer S joined; its first message was a snapshot with version 0
the scorekeeper sent 1 point for Owls; its reply was a snapshot with version 1
the scorekeeper sent 2 points for Foxes; its reply was a snapshot with version 2
the scorekeeper sent 3 points for Herons; its reply was a snapshot with version 3
score 1: the reply came after the server finished sending version 1 to S
score 2: the reply came after the server finished sending version 2 to S
score 3: the reply came after the server finished sending version 3 to S
the server shut down
viewer A received versions [0, 1, 2, 3], then the server closed it with 1001
viewer S received versions [0, 1, 2, 3], then the server closed it with 1001
Jede Antwort kam, nachdem das langsame Senden an S fertig war. Das Protokoll meldet Reihenfolge, nicht Zeit, weil die Reihenfolge das ist, was dein Code entscheidet. Das Bild zeigt eine Wertung, die Zeit läuft nach unten: links der Thread des Punktezählers, rechts ein Zuschauer, der beitreten will. Achte darauf, wie lange das Lock der Tafel gehalten bleibt und worauf beide Seiten warten.

In Worten: Der Thread des Punktezählers nimmt das Lock der Tafel, sendet das Update an A, sendet es dann an S, was 0,3 s dauert, und gibt erst dann das Lock frei und sendet seine Antwort. Ein Zuschauer, der in dieser Zeit beitreten will, wartet ebenfalls auf das Lock und tritt erst nach der Freigabe bei.
Alle warten auf den Langsamsten
Zähle, wer für S bezahlt. Der Punktezähler, weil seine Antwort nach dem ganzen Broadcast kommt. Jeder Zuschauer, der nach S registriert wurde, weil die add_viewer das Lock braucht, das der Broadcast hält. Bei tausend Zuschauern gibt der langsamste das Tempo für alle vor, und ein echter Server kann nicht aussuchen, welche Clients sich mit ihm verbinden.
Bei v1 dauert jedes Senden an S 0,3 s. Wer wartet, wenn der Punktezähler wertet?
Was es beheben könnte
Es gibt drei Auswege. Der Server könnte einen langsamen Zuschauer trennen, was hart ist für einen Zuschauer, der nur kurz hinterherhinkt. Er könnte für jeden Zuschauer eine Warteschlange mit jedem Update führen und sie senden, wenn er kann, aber bei einem Zuschauer, der nie aufholt, wächst seine Warteschlange ohne Grenze. Oder er könnte für jeden Zuschauer nur den neuesten Snapshot behalten. Ein Snapshot enthält die ganze Tafel, also ersetzt der neueste jeden älteren, und ein langsamer Zuschauer, der eine Version überspringt, verpasst nichts, was er braucht.
Warum darf die Anzeigetafel für einen langsamen Zuschauer ein Update überspringen?
Die Anzeigetafel nimmt den dritten Weg. Lektion 2 baut für einen Zuschauer einen Behälter, der einen einzigen Snapshot enthält, den neuesten, und den Thread weckt, der ihn senden wird.