diff --git a/ssh/channel.go b/ssh/channel.go index 6737996..afc9aef 100644 --- a/ssh/channel.go +++ b/ssh/channel.go @@ -634,7 +634,10 @@ func (ch *channel) SendRequest(name string, wantReply bool, payload []byte) (boo drain: for { select { - case <-ch.msg: + case _, ok := <-ch.msg: + if !ok { + break drain + } default: break drain } diff --git a/ssh/mux_test.go b/ssh/mux_test.go index 65d22be..48a8da6 100644 --- a/ssh/mux_test.go +++ b/ssh/mux_test.go @@ -10,6 +10,7 @@ import ( "io" "sync" "testing" + "time" ) func muxPair() (*mux, *mux) { @@ -1366,3 +1367,41 @@ func TestChannelResponseAcceptedWhilePending(t *testing.T) { writer.Close() <-serverDone } + +func TestChannelSendRequestAfterCloseNoSpinloop(t *testing.T) { + writer, reader, mux := channelPair(t) + defer writer.Close() + defer mux.Close() + + // Close the channel, this closes reader.msg. + if err := reader.Close(); err != nil { + t.Fatalf("Close: %v", err) + } + // Wait for reader.msg to actually be closed by the mux loop. + for { + select { + case _, ok := <-reader.msg: + if !ok { + goto closed + } + case <-time.After(2 * time.Second): + t.Fatal("reader.msg was not closed") + } + } +closed: + + done := make(chan error, 1) + go func() { + _, err := reader.SendRequest("ping", true, nil) + done <- err + }() + + select { + case err := <-done: + if !errors.Is(err, io.EOF) { + t.Fatalf("SendRequest after close: got %v, want io.EOF", err) + } + case <-time.After(2 * time.Second): + t.Fatal("SendRequest spinloop on closed ch.msg") + } +}