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