Skip to content

Data race in websocket reconnect: concurrent ReadMessage and SetPingHandler #800

@aleksey-kravtsov

Description

@aleksey-kravtsov

There is a reproducible data race in go-binance websocket implementation when reconnecting, detected by Go race detector.

The race happens between:
(*websocket.Conn).ReadMessage() / advanceFrame()
(*websocket.Conn).SetPingHandler()

Both operate on the same *websocket.Conn concurrently.
According to gorilla/websocket documentation, connection handlers (SetPingHandler, SetPongHandler) must not be modified concurrently with reads.
This race is not visible without -race, but is consistently reproducible when forcing reconnects.

Minimal reproduce:

package main

import (
	"log"
	"time"

	binance "github.com/adshao/go-binance/v2"
)

func main() {
	symbols := []string{"BTCUSDT"}

	for {
		doneC, stopC, err := binance.WsCombinedBookTickerServe(
			symbols,
			func(event *binance.WsBookTickerEvent) {},
			func(err error) {
				log.Println("ws error:", err)
			},
		)
		if err != nil {
			log.Fatal(err)
		}

		select {
		case <-doneC:
		case <-time.After(20 * time.Second):
		}

		// Force reconnect
		close(stopC)

		time.Sleep(500 * time.Millisecond)
	}
}
go run -race main.go

Stack trace:

2026/01/24 18:01:07 starting ws...
2026/01/24 18:01:18 forcing reconnect
2026/01/24 18:01:19 starting ws...
2026/01/24 18:01:31 forcing reconnect
2026/01/24 18:01:31 starting ws...
2026/01/24 18:01:43 forcing reconnect
2026/01/24 18:01:44 starting ws...
2026/01/24 18:01:55 forcing reconnect
2026/01/24 18:01:56 starting ws...
==================
WARNING: DATA RACE
Read at 0x00c0002803e0 by goroutine 53:
  github.com/gorilla/websocket.(*Conn).advanceFrame()
      /Users/alekseyk/go/pkg/mod/github.com/gorilla/websocket@v1.5.3/conn.go:954 +0x1e24
  github.com/gorilla/websocket.(*Conn).NextReader()
      /Users/alekseyk/go/pkg/mod/github.com/gorilla/websocket@v1.5.3/conn.go:1009 +0x168
  github.com/gorilla/websocket.(*Conn).ReadMessage()
      /Users/alekseyk/go/pkg/mod/github.com/gorilla/websocket@v1.5.3/conn.go:1093 +0x50
  github.com/adshao/go-binance/v2.init.func1.1()
      /Users/alekseyk/go/pkg/mod/github.com/adshao/go-binance/v2@v2.8.10/websocket.go:96 +0x4f4

Previous write at 0x00c0002803e0 by goroutine 54:
  github.com/gorilla/websocket.(*Conn).SetPingHandler()
      /Users/alekseyk/go/pkg/mod/github.com/gorilla/websocket@v1.5.3/conn.go:1170 +0xdc
  github.com/adshao/go-binance/v2.keepAliveWithPong()
      /Users/alekseyk/go/pkg/mod/github.com/adshao/go-binance/v2@v2.8.10/websocket.go:151 +0x230
  github.com/adshao/go-binance/v2.wsServe.func1()
      /Users/alekseyk/go/pkg/mod/github.com/adshao/go-binance/v2@v2.8.10/websocket.go:39 +0x84
  github.com/adshao/go-binance/v2.init.func1.1.gowrap2()
      /Users/alekseyk/go/pkg/mod/github.com/adshao/go-binance/v2@v2.8.10/websocket.go:80 +0x78

Goroutine 53 (running) created at:
  github.com/adshao/go-binance/v2.init.func1()
      /Users/alekseyk/go/pkg/mod/github.com/adshao/go-binance/v2@v2.8.10/websocket.go:69 +0x770
  github.com/adshao/go-binance/v2.wsServe()
      /Users/alekseyk/go/pkg/mod/github.com/adshao/go-binance/v2@v2.8.10/websocket.go:35 +0xa0
  github.com/adshao/go-binance/v2.WsCombinedBookTickerServe()
      /Users/alekseyk/go/pkg/mod/github.com/adshao/go-binance/v2@v2.8.10/websocket_service.go:1078 +0x3a0
  main.main()
      /Users/alekseyk/Projects/binance_race_on_reconnect/main.go:20 +0x210

Goroutine 54 (running) created at:
  github.com/adshao/go-binance/v2.init.func1.1()
      /Users/alekseyk/go/pkg/mod/github.com/adshao/go-binance/v2@v2.8.10/websocket.go:80 +0x2f4
==================
==================
WARNING: DATA RACE
Write at 0x00c0001145f8 by goroutine 53:
  ??()
      -:0 +0x100e52728
  sync/atomic.StoreInt64()
      <autogenerated>:1 +0x14
  github.com/gorilla/websocket.(*Conn).advanceFrame()
      /Users/alekseyk/go/pkg/mod/github.com/gorilla/websocket@v1.5.3/conn.go:954 +0x1e3c
  github.com/gorilla/websocket.(*Conn).NextReader()
      /Users/alekseyk/go/pkg/mod/github.com/gorilla/websocket@v1.5.3/conn.go:1009 +0x168
  github.com/gorilla/websocket.(*Conn).ReadMessage()
      /Users/alekseyk/go/pkg/mod/github.com/gorilla/websocket@v1.5.3/conn.go:1093 +0x50
  github.com/adshao/go-binance/v2.init.func1.1()
      /Users/alekseyk/go/pkg/mod/github.com/adshao/go-binance/v2@v2.8.10/websocket.go:96 +0x4f4

Previous write at 0x00c0001145f8 by goroutine 54:
  github.com/adshao/go-binance/v2.keepAliveWithPong()
      /Users/alekseyk/go/pkg/mod/github.com/adshao/go-binance/v2@v2.8.10/websocket.go:148 +0xfc
  github.com/adshao/go-binance/v2.wsServe.func1()
      /Users/alekseyk/go/pkg/mod/github.com/adshao/go-binance/v2@v2.8.10/websocket.go:39 +0x84
  github.com/adshao/go-binance/v2.init.func1.1.gowrap2()
      /Users/alekseyk/go/pkg/mod/github.com/adshao/go-binance/v2@v2.8.10/websocket.go:80 +0x78

Goroutine 53 (running) created at:
  github.com/adshao/go-binance/v2.init.func1()
      /Users/alekseyk/go/pkg/mod/github.com/adshao/go-binance/v2@v2.8.10/websocket.go:69 +0x770
  github.com/adshao/go-binance/v2.wsServe()
      /Users/alekseyk/go/pkg/mod/github.com/adshao/go-binance/v2@v2.8.10/websocket.go:35 +0xa0
  github.com/adshao/go-binance/v2.WsCombinedBookTickerServe()
      /Users/alekseyk/go/pkg/mod/github.com/adshao/go-binance/v2@v2.8.10/websocket_service.go:1078 +0x3a0
  main.main()
      /Users/alekseyk/Projects/binance_race_on_reconnect/main.go:20 +0x210

Goroutine 54 (running) created at:
  github.com/adshao/go-binance/v2.init.func1.1()
      /Users/alekseyk/go/pkg/mod/github.com/adshao/go-binance/v2@v2.8.10/websocket.go:80 +0x2f4
==================


Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions