Skip to content

net/http: gorilla/websocket.Upgrade will be blocked in http.(*response).Hijack #20963

@nd-lj

Description

@nd-lj

Please answer these questions before submitting your issue. Thanks!

What version of Go are you using (go version)?

go version go1.8.3 windows/amd64

What operating system and processor architecture are you using (go env)?

set GOARCH=amd64
set GOBIN=
set GOEXE=.exe
set GOHOSTARCH=amd64
set GOHOSTOS=windows (win7)
set GOOS=windows

What did you do?

i use gorilla/websocket, and when Upgrade, it will be blocked in Hijack. the sample code of echo in gorilla/websocket will blocked sometimes on windows, and if i add a line time.Sleep(10 * time.Millisecond) before Upgrade, then it will almost 100% reproduce.

the blocked goroutine stack just like this:

goroutine 36 [semacquire]:
sync.runtime_notifyListWait(0xc04214a190, 0x0)
	D:/Go/src/runtime/sema.go:298 +0x119
sync.(*Cond).Wait(0xc04214a180)
	D:/Go/src/sync/cond.go:57 +0x90
net/http.(*connReader).abortPendingRead(0xc04214a080)
	D:/Go/src/net/http/server.go:686 +0xc6
net/http.(*conn).hijackLocked(0xc04213c000, 0x7f0f68, 0xc04213c090, 0x338270, 0xc04215f9b0, 0x40de64)
	D:/Go/src/net/http/server.go:292 +0x5a
net/http.(*response).Hijack(0xc04208e0e0, 0x0, 0x0, 0x0, 0x0, 0x0)
	D:/Go/src/net/http/server.go:1892 +0x103
github.com/gorilla/websocket.(*Upgrader).Upgrade(0x98d9c0, 0x9609c0, 0xc04208e0e0, 0xc042142100, 0x0, 0x0, 0x1, 0x1)
	e:/gopath/src/github.com/gorilla/websocket/server.go:164 +0x3fe
nd/sparklet/net3.StartWebSocketServer.func1(0x9609c0, 0xc04208e0e0, 0xc042142100)
	E:/work/goweb/src/nd/sparklet/net3/api.go:63 +0x10f
net/http.HandlerFunc.ServeHTTP(0xc042043b70, 0x9609c0, 0xc04208e0e0, 0xc042142100)
	D:/Go/src/net/http/server.go:1942 +0x4b
net/http.(*ServeMux).ServeHTTP(0xc0420717d0, 0x9609c0, 0xc04208e0e0, 0xc042142100)
	D:/Go/src/net/http/server.go:2238 +0x137
net/http.serverHandler.ServeHTTP(0xc042118000, 0x9609c0, 0xc04208e0e0, 0xc042142100)
	D:/Go/src/net/http/server.go:2568 +0x99
net/http.(*conn).serve(0xc04213c000, 0x960fc0, 0xc04214a040)
	D:/Go/src/net/http/server.go:1825 +0x619
created by net/http.(*Server).Serve
	D:/Go/src/net/http/server.go:2668 +0x2d5

What did you expect to see?

Upgrade will return soon

What did you see instead?

Upgrade blocked forever

similar discussion in gorilla/websocket, please see this issue

the sample whole code is below:

it seems will only blocked on windows, it runs fine on linux.

server.go

package main

import (
	"flag"
	"html/template"
	"log"
	"net/http"

	"github.com/gorilla/websocket"
	"time"
)

var addr = flag.String("addr", "localhost:8080", "http service address")

var upgrader = websocket.Upgrader{} // use default options

func echo(w http.ResponseWriter, r *http.Request) {
	time.Sleep(10 * time.Millisecond)

	c, err := upgrader.Upgrade(w, r, nil)  //this is will be blocked in Hijack!!!!!!
	if err != nil {
		log.Print("upgrade:", err)
		return
	}
	defer c.Close()
	for {
		mt, message, err := c.ReadMessage()
		if err != nil {
			log.Println("read:", err)
			break
		}
		log.Printf("recv: %s", message)
		err = c.WriteMessage(mt, message)
		if err != nil {
			log.Println("write:", err)
			break
		}
	}
}

func home(w http.ResponseWriter, r *http.Request) {
	homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
}

func main() {
	flag.Parse()
	log.SetFlags(0)
	http.HandleFunc("/echo", echo)
	http.HandleFunc("/", home)
	log.Fatal(http.ListenAndServe(*addr, nil))
}

var homeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script>  
window.addEventListener("load", function(evt) {

    var output = document.getElementById("output");
    var input = document.getElementById("input");
    var ws;

    var print = function(message) {
        var d = document.createElement("div");
        d.innerHTML = message;
        output.appendChild(d);
    };

    document.getElementById("open").onclick = function(evt) {
        if (ws) {
            return false;
        }
        ws = new WebSocket("{{.}}");
        ws.onopen = function(evt) {
            print("OPEN");
        }
        ws.onclose = function(evt) {
            print("CLOSE");
            ws = null;
        }
        ws.onmessage = function(evt) {
            print("RESPONSE: " + evt.data);
        }
        ws.onerror = function(evt) {
            print("ERROR: " + evt.data);
        }
        return false;
    };

    document.getElementById("send").onclick = function(evt) {
        if (!ws) {
            return false;
        }
        print("SEND: " + input.value);
        ws.send(input.value);
        return false;
    };

    document.getElementById("close").onclick = function(evt) {
        if (!ws) {
            return false;
        }
        ws.close();
        return false;
    };

});
</script>
</head>
<body>
<table>
<tr><td valign="top" width="50%">
<p>Click "Open" to create a connection to the server, 
"Send" to send a message to the server and "Close" to close the connection. 
You can change the message and send multiple times.
<p>
<form>
<button id="open">Open</button>
<button id="close">Close</button>
<p><input id="input" type="text" value="Hello world!">
<button id="send">Send</button>
</form>
</td><td valign="top" width="50%">
<div id="output"></div>
</td></tr></table>
</body>
</html>
`))

client.go

package main

import (
	"flag"
	"log"
	"net/url"
	"os"
	"os/signal"
	"time"

	"github.com/gorilla/websocket"
)

var addr = flag.String("addr", "localhost:8080", "http service address")

func main() {
	flag.Parse()
	log.SetFlags(0)

	interrupt := make(chan os.Signal, 1)
	signal.Notify(interrupt, os.Interrupt)

	u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo"}
	log.Printf("connecting to %s", u.String())

	c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
	if err != nil {
		log.Fatal("dial:", err)
	}
	defer c.Close()

	done := make(chan struct{})

	go func() {
		defer c.Close()
		defer close(done)
		for {
			_, message, err := c.ReadMessage()
			if err != nil {
				log.Println("read:", err)
				return
			}
			log.Printf("recv: %s", message)
		}
	}()

	ticker := time.NewTicker(time.Second)
	defer ticker.Stop()

	for {
		select {
		case t := <-ticker.C:
			err := c.WriteMessage(websocket.TextMessage, []byte(t.String()))
			if err != nil {
				log.Println("write:", err)
				return
			}
		case <-interrupt:
			log.Println("interrupt")
			// To cleanly close a connection, a client should send a close
			// frame and wait for the server to close the connection.
			err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
			if err != nil {
				log.Println("write close:", err)
				return
			}
			select {
			case <-done:
			case <-time.After(time.Second):
			}
			c.Close()
			return
		}
	}
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    FrozenDueToAgeNeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions