Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
boquan58 opened this issue Jul 10, 2017 · 8 comments

Comments

Projects
None yet
5 participants
@boquan58
Copy link

commented Jul 10, 2017

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
		}
	}
}

@boquan58 boquan58 changed the title websocket.Upgrade will blocked in http.(*response).Hijack websocket.Upgrade will be blocked in http.(*response).Hijack Jul 10, 2017

@mikioh mikioh changed the title websocket.Upgrade will be blocked in http.(*response).Hijack net/http: gorilla/websocket.Upgrade will be blocked in http.(*response).Hijack Jul 10, 2017

@mikioh

This comment has been minimized.

Copy link
Contributor

commented Jul 10, 2017

@boquan58,

Sorry, this is the issue tracker for Go. Looks like your issue depends on the external package: https://godoc.org/github.com/gorilla/websocket. Probably It's better to ask your question at https://github.com/gorilla/websocket/issues.

FYI: https://github.com/golang/go/wiki/Questions

@mikioh mikioh closed this Jul 10, 2017

@boquan58

This comment has been minimized.

Copy link
Author

commented Jul 10, 2017

@mikioh
are you kidiing me. did you read the issue seriously?

@mikioh

This comment has been minimized.

Copy link
Contributor

commented Jul 10, 2017

@boquan58,

did you read the issue seriously?

Nope, sorry. If you think the net/http package has some issue, can you please provide a simple reproducible example not using external packages like gorilla/websocket?

@mikioh mikioh reopened this Jul 10, 2017

@bradfitz bradfitz added this to the Go1.10 milestone Jul 10, 2017

@profer

This comment has been minimized.

Copy link

commented Jul 26, 2017

I am encountering the same issue where Hijack gets stuck in (*connReader).abortPendingRead on macOS. This problem is specific to Go 1.8 and up – including 1.9rc1. When building with Go 1.7.6 Hijack behaves as expected.

@profer

This comment has been minimized.

Copy link

commented Jul 27, 2017

Nevermind – I've found the cause for my deadlock in #15224. The http1 server in go 1.8 and 1.9 relies on the correct implementation of the SetReadDeadline and SetWriteDeadline functions on the underlying net.Conn. The expected behaviour was documented for go 1.8 in #17982.

In the net.Conn implementation I was using, SetReadDeadline did not affect pending reads. This causes the http1 server to get stuck in abortPendingRead because the pending read never fails with a timeout error.

@bradfitz

This comment has been minimized.

Copy link
Member

commented Jul 27, 2017

@boquan58, same for you perhaps?

@boquan58

This comment has been minimized.

Copy link
Author

commented Jul 28, 2017

sorry, i'm not sure, maybe it's the problem of my win7 os, the same binary work fine on others win7 os. and it also works fine on my pc after i reinstall win7
i will close it, thanks

@boquan58 boquan58 closed this Jul 28, 2017

@bradfitz

This comment has been minimized.

Copy link
Member

commented Jul 28, 2017

With Windows mysteries, 9 times out of 10, the answer is anti-virus or malware hooking system calls poorly.

@golang golang locked and limited conversation to collaborators Jul 28, 2018

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
You can’t perform that action at this time.