Closed
Description
- net/http.persistConn.readLoop: If the response has no body, putIdleConn will be called immediately so another request can reuse the persistConn, then setReqCanceler(rc.req, nil) is called.
- The problem is: If persistConn is reused by another-request and CancelRequest(old-request) is called before setReqCanceler(old-request, nil), the another-request will failed.
- Source code: https://github.com/golang/go/blob/release-branch.go1.4/src/net/http/transport.go#L920
if alive && !hasBody {
alive = !pc.sawEOF &&
pc.wroteRequest() &&
pc.t.putIdleConn(pc) // !!!!! 'pc' can be reused after function called.
}
rc.ch <- responseAndError{resp, err}
// Wait for the just-returned response body to be fully consumed
// before we race and peek on the underlying bufio reader.
if waitForBodyRead != nil {
select {
case alive = <-waitForBodyRead:
case <-pc.closech:
alive = false
}
}
pc.t.setReqCanceler(rc.req, nil) // !!!!! CancelRequest may be invoked before function called
if !alive {
pc.close()
}
- Reproduce, client will print "Get http://localhost:12345: net/http: transport closed before response was received"
diff --git a/src/pkg/net/http/transport.go b/src/pkg/net/http/transport.go
index b1cc632..de3d15f 100644
--- a/src/pkg/net/http/transport.go
+++ b/src/pkg/net/http/transport.go
@@ -860,6 +860,8 @@ func (pc *persistConn) readLoop() {
pc.t.putIdleConn(pc)
}
+ time.Sleep(10e9) // to simulate an delayed schedule(for easy reproduce)
+
rc.ch <- responseAndError{resp, err}
// Wait for the just-returned response body to be fully consumed
// server
package main
import (
"fmt"
"net/http"
"time"
)
func main() {
var i int
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
i++
if i > 1 {
time.Sleep(5e9)
}
})
err := http.ListenAndServe(":12345", nil)
fmt.Println("ListenAndServer", err)
}
// client
package main
import (
"fmt"
"net/http"
"time"
)
func main() {
tr := &http.Transport{}
client := &http.Client{
Transport: tr,
}
done := make(chan error, 1)
requestCanceled, _ := http.NewRequest("GET", "http://localhost:12345", nil)
requestIrrelevant, _ := http.NewRequest("GET", "http://localhost:12345", nil)
go func() {
time.Sleep(2e9) // waiting for requestToBeCanceled to be responsed
go func() {
time.Sleep(2e9) // waiting for requestIrrelevant to reuse the tcp connection
tr.CancelRequest(requestCanceled)
}()
_, err := client.Do(requestIrrelevant)
done <- err
}()
client.Do(requestCanceled)
errIrrelevant := <-done
if errIrrelevant != nil {
fmt.Println(errIrrelevant)
}
}