Fix "send on closed channel" panic (#13745)

The httpStreamResponse should not return until CloseWithError has been called.

Instead keep track of write state and skip writing/flushing if an error has occurred.

Fixes #13743

Regression from #13597 (not released)
This commit is contained in:
Klaus Post 2021-11-24 09:42:42 -08:00 committed by GitHub
parent 9ca25bd48f
commit fe3e47b1e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -951,44 +951,46 @@ func streamHTTPResponse(w http.ResponseWriter) *httpStreamResponse {
blockCh := make(chan []byte)
h := httpStreamResponse{done: doneCh, block: blockCh}
go func() {
defer close(doneCh)
var canWrite = true
write := func(b []byte) {
if canWrite {
n, err := w.Write(b)
if err != nil || n != len(b) {
canWrite = false
}
}
}
ticker := time.NewTicker(time.Second * 10)
defer ticker.Stop()
for {
select {
case <-ticker.C:
// Response not ready, write a filler byte.
_, err := w.Write([]byte{32})
if err != nil {
return
}
write([]byte{32})
if canWrite {
w.(http.Flusher).Flush()
}
case err := <-doneCh:
if err != nil {
_, werr := w.Write([]byte{1})
if werr != nil {
return
}
w.Write([]byte(err.Error()))
write([]byte{1})
write([]byte(err.Error()))
} else {
w.Write([]byte{0})
write([]byte{0})
}
close(doneCh)
return
case block := <-blockCh:
var tmp [5]byte
tmp[0] = 2
binary.LittleEndian.PutUint32(tmp[1:], uint32(len(block)))
_, err := w.Write(tmp[:])
if err != nil {
return
}
_, err = w.Write(block)
if err != nil {
return
}
write(tmp[:])
write(block)
if canWrite {
w.(http.Flusher).Flush()
}
}
}
}()
return &h
}