chyeh chyeh - 7 months ago 63
HTTP Question

http.ResponseWriter.WriteHeader() caused deadlock

I was trying to use the

httptest
package in golang. I found out something I don't understand. Here is the code:

package main

import (
"fmt"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
)

func main() {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello1"))
}))
ts.Close()
ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello2"))
}))
ts.Close()
ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(100)
w.Write([]byte("Hello3"))
}))

res, err := http.Get(ts.URL)
if err != nil {
log.Fatal(err)
}
greeting, err := ioutil.ReadAll(res.Body)
res.Body.Close()
if err != nil {
log.Fatal(err)
}

ts.Close()
fmt.Printf("%s", greeting)
}


In this code example, I was trying to open and close
httptest
servers several times. Somehow it caused deadlock in The Go Playground. I tried on my own environment (Go version: go1.7.4 darwin/amd64) and it caused hanging without responding at all.

My question is: Why
w.WriteHeader(100)
caused deadlock but
w.WriteHeader(200)
doesn't? Is it the bug from the core library of Golang or just I misunderstood some usage? Tks!

Answer Source

If you slightly modify code:

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "net/http/httptest"
)

func main() {
    ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello1"))
    }))
    ts.Close()
    ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello2"))
    }))
    ts.Close()
    ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(100)
        w.Write([]byte("Hello3"))
    }))

    fmt.Println("before get")   ///// . <----
    res, err := http.Get(ts.URL)
    fmt.Println("after get")    ///// . <----
    if err != nil {
        log.Fatal(err)
    }
    greeting, err := ioutil.ReadAll(res.Body)
    res.Body.Close()
    if err != nil {
        log.Fatal(err)
    }

    ts.Close()
    fmt.Printf("%s", greeting)
}

and run it - you'll see only first line.

That means Go http client want more data from you. So it hangs on line

    res, err := http.Get(ts.URL)

and cannot get to the ts.Close() below.

Next - lets modify a test, so it will close a connection and this way release a clients waiting lock:

ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(100)
    w.Write([]byte("Hello3"))
    hj, _ := w.(http.Hijacker)
    conn, _, _ := hj.Hijack()
    conn.Close()
}))

I close connection explicitly but this way you get both check strings and test finishes ok. Try it.

Of course it's a HTTP protocol violation so I get an error:

Get http://127.0.0.1:54243: net/http: HTTP/1.x transport connection broken: unexpected EOF
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download