func (pc *persistConn) readLoop() { // eofc is used to block http.Handler goroutines reading from Response.Body // at EOF until this goroutines has (potentially) added the connection // back to the idle pool. eofc := make(chan struct{}) defer close(eofc) // unblock reader on errors // Read this once, before loop starts. (to avoid races in tests) testHookMu.Lock() testHookReadLoopBeforeNextRead := testHookReadLoopBeforeNextRead testHookMu.Unlock() alive := true for alive { pb, err := pc.br.Peek(1) pc.lk.Lock() if pc.numExpectedResponses == 0 { if !pc.closed { pc.closeLocked() if len(pb) > 0 { log.Printf("Unsolicited response received on idle HTTP channel starting with %q; err=%v", string(pb), err) } } pc.lk.Unlock() return } pc.lk.Unlock() rc := <-pc.reqch var resp *http.Response if err == nil { resp, err = http.ReadResponse(pc.br, rc.req) if err == nil && resp.StatusCode == 100 { // Skip any 100-continue for now. // TODO(bradfitz): if rc.req had "Expect: 100-continue", // actually block the request body write and signal the // writeLoop now to begin sending it. (Issue 2184) For now we // eat it, since we're never expecting one. resp, err = http.ReadResponse(pc.br, rc.req) } } if resp != nil { resp.TLS = pc.tlsState } hasBody := resp != nil && rc.req.Method != "HEAD" && resp.ContentLength != 0 if err != nil { pc.close() } else { resp.Body = &bodyEOFSignal{body: resp.Body} } if err != nil || resp.Close || rc.req.Close || resp.StatusCode <= 199 { // Don't do keep-alive on error if either party requested a close // or we get an unexpected informational (1xx) response. // StatusCode 100 is already handled above. alive = false } var waitForBodyRead chan bool // channel is nil when there's no body if hasBody { waitForBodyRead = make(chan bool, 2) resp.Body.(*bodyEOFSignal).earlyCloseFn = func() error { waitForBodyRead <- false return nil } resp.Body.(*bodyEOFSignal).fn = func(err error) error { isEOF := err == io.EOF waitForBodyRead <- isEOF if isEOF { <-eofc // see comment at top } else if err != nil && pc.isCanceled() { return errRequestCanceled } return err } } pc.lk.Lock() pc.numExpectedResponses-- pc.lk.Unlock() // The connection might be going away when we put the // idleConn below. When that happens, we close the response channel to signal // to roundTrip that the connection is gone. roundTrip waits for // both closing and a response in a select, so it might choose // the close channel, rather than the response. // We send the response first so that roundTrip can check // if there is a pending one with a non-blocking select // on the response channel before erroring out. rc.ch <- responseAndError{resp, err} if hasBody { // To avoid a race, wait for the just-returned // response body to be fully consumed before peek on // the underlying bufio reader. select { case bodyEOF := <-waitForBodyRead: pc.t.setReqCanceler(rc.req, nil) // before pc might return to idle pool alive = alive && bodyEOF && !pc.sawEOF && pc.wroteRequest() && pc.t.putIdleConn(pc) if bodyEOF { eofc <- struct{}{} } case <-pc.closech: alive = false } } else { pc.t.setReqCanceler(rc.req, nil) // before pc might return to idle pool alive = alive && !pc.sawEOF && pc.wroteRequest() && pc.t.putIdleConn(pc) } if hook := testHookReadLoopBeforeNextRead; hook != nil { hook() } } pc.close() }