All cooperating goroutines regularly try to read from the shared "poison"
channel. If the read succeeds, they exit by calling die(), assuming
somebody else cracked open the poison pill.
When any of these goroutines is done with its job, it signals other
goroutines to exit by calling open_poison() on the shared channel.
This approach takes advantage of the fact that reads from a closed
channel always succeed.
The driving goroutine (Client.Loop() in this case), is called from the
"main" goroutine. And because when the "main" goroutine exits, the whole
program exit (using os.Exit()) irrepective of liveness of other goroutines,
we could not use the same "poison" channel to wait in the driving goroutine.
Instead, we use sync.WaitGroup to wait for spawned goroutines, because
we want the spawned goroutines to cleanup and exit cleanly.
Forward the EOF to GoTTY, and let the server-side decide if it wants to
terminate the connection. The server closes the connection, and in
response we terminate the readLoop which in turn signals writeLoop to
terminate via the QuitChan.
This allows for the user to pipe commands to gotty-client, and capture
all the result sent by the server. For eg. when gotty is launched as
`gotty -w bash`, the following command would now wait to capture all
output from the server.
for (( i = 0 ; i < 2; ++i )); do echo echo $i; echo sleep 2; done | ./gotty-client https://gotty.example.com
Before this patch, gotty-client used to exit on encountering EOF from
the left side of the pipe.