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
Closed
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone

Comments

@boquan58
Copy link

boquan58 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
Copy link
Contributor

mikioh 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 as completed Jul 10, 2017
@boquan58
Copy link
Author

boquan58 commented Jul 10, 2017

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

@mikioh
Copy link
Contributor

mikioh 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
@bradfitz bradfitz added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Jul 10, 2017
@profer
Copy link

profer 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
Copy link

profer 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
Copy link
Contributor

@boquan58, same for you perhaps?

@boquan58
Copy link
Author

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

@bradfitz
Copy link
Contributor

bradfitz 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.
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
None yet
Development

No branches or pull requests

5 participants