EOF error on ws.Upgrade
I receive EOF error on ws.Upgrade during high load on a server. I took code from example and run ~2k concurrent connections to websocket server. Another thing is that I pass JWT token in URL.
It seems that readLine inside Upgrade is causing error: https://github.com/gobwas/ws/blob/master/server.go#L452
and then error is returned from here: https://github.com/gobwas/ws/blob/master/util.go#L183
Example code to reproduce the issue: Server:
package main
import (
"log"
"net"
"github.com/gobwas/ws"
)
func main() {
ln, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatal(err)
}
for {
conn, err := ln.Accept()
if err != nil {
log.Fatal("accept err", err)
continue
}
_, err = ws.Upgrade(conn)
if err != nil {
log.Fatal("upgrade err", err)
continue
}
}
}
client:
package main
import (
"fmt"
"log"
"sync"
"github.com/gorilla/websocket"
)
func main() {
for {
var wg sync.WaitGroup
for i := 0; i < 2000; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
url := "ws://localhost:8080/ws?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
_, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
err = fmt.Errorf("cannot dial, err: %w", err)
log.Println(err)
return
}
return
}(i)
}
wg.Wait()
fmt.Println("done")
}
}
This issue maybe relates to tcp config,check somaxconn size and fd size in your environment.
somaxconn:
kern.ipc.somaxconn: 18000
$ ulimit -a
Maximum size of core files created (kB, -c) 0
Maximum size of a process’s data segment (kB, -d) unlimited
Maximum size of files created by the shell (kB, -f) unlimited
Maximum size that may be locked into memory (kB, -l) unlimited
Maximum resident set size (kB, -m) unlimited
Maximum number of open file descriptors (-n) 8000
Maximum stack size (kB, -s) 8176
Maximum amount of cpu time in seconds (seconds, -t) unlimited
Maximum number of processes available to a single user (-u) 2666
Maximum amount of virtual memory available to the shell (kB, -v) unlimited
It seems that messages are cut off when reading from connection, for example:
...Upgrade\r\nSec-WebSocket-Key: 122CV3QQHRFoIKR44OzQAA==\r\nSec-WebSocket-Version: 13\r\nUpgrade:"
Each message is 512 bytes-long.
Can I ask for a guide, what are the other settings I should check? I'm out of ideas right now
ln, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatal(err)
}
Try to use http server,like Readme shows,because websocket bases on http 1.1or above version not tcp. If you want to get token before Upgrade,I give a reference below
type Token struct {
Token string `json:"token" bingding:"required"`
}
var param Token
if err := c.ShouldBindJSON(¶m); err != nil {
return
}
log.Println("token=", param.Token)
c in code is *gin.Context
Thank you, I will give it a try.
The server I posted above is from README "The lower-level example without wsutil" example. Http 1.1 uses tcp behind the scenes. According to this article: https://www.freecodecamp.org/news/million-websockets-and-go-cc58418460bb/ it is fine to use net.Listen and Accept along with gobwas/ws package (without wsutil).
I wonder if the example from README is sufficient to handle mentioned a million websockets connections.
Sorry,I didn't notice that example,maybe lower-level only supports pure url without parameters?
Unfortunately, I think ws.Upgrade has some issues. Here is simpler example.
Server:
package main
import (
"fmt"
"net"
"github.com/gobwas/ws"
)
func main() {
ln, err := net.Listen("tcp", ":8111")
if err != nil {
panic(err)
}
for {
conn, err := ln.Accept()
if err != nil {
fmt.Println("accept err", err)
continue
}
_, err = ws.Upgrade(conn)
if err != nil {
fmt.Println("upgrade", err)
}
}
}
client:
package main
import (
"context"
"fmt"
"sync"
"time"
"github.com/gorilla/websocket"
)
func main() {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
_, _, err := websocket.DefaultDialer.DialContext(ctx, "ws://localhost:8111", nil)
if err != nil {
fmt.Println(err)
}
}()
}
wg.Wait()
}
There are no params in URL, but I receive upgrade EOF. For some reason, this method returns EOF: https://github.com/gobwas/ws/blob/master/util.go#L169.
This is non-deterministic. I have to run client code few times to observe EOF error
这个情况确实是必现的。因为你的client在链接建立后,就自己销毁了。 相当于客户端主动断开链接。 server upgrade 失败 EOF。这个应该是合理的。就是会打印日志
I'm having similar issue - reading from web socket server returns EOF... consistently (first read after ws.Upgrade) Looks like this lib is unusable in the current form, should'a went with gorilla instead... but it's archived now