1

I have a WebSocket client that currently has a data race when connecting to the WS server. The WS client works by connecting to the server, listening to the server connection, and then subscribing to specific WS endpoints.

If it matters, I'm using a custom wrapper that I created on top of the SAC007 WS client (I've made a few small modifications to the SAC007 client as well, in order to fit my needs better, but nothing that should cause a data race) and currently trying to understand what I'm doing wrong to get a race, but have no luck with understanding the issue so far. Would appreciate any help understanding what I'm doing wrong

Here is my main program:

func main() {
    // check if in production or testing mode
    var testing bool = true
    args := os.Args
    initClient(args, &testing, &stored_data.Base_currency)

    // concurrency handling
    comms := make(chan os.Signal, 1)
    signal.Notify(comms, os.Interrupt, syscall.SIGTERM)
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    var wg sync.WaitGroup

    // set ohlc interval(s) and pair(s)
    OHLCinterval := []int{1, 5}
    pairs := []string{"BTC/" + stored_data.Base_currency, "ETH/" + stored_data.Base_currency, "EOS/" + stored_data.Base_currency}

    // create ws connections
    wg.Add(1)
    pubSocket, err := ws_client.ConnectToServer(&wg, "public", testing)
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    // create websocket channel(s)
    pubCh := make(chan interface{})
    defer close(pubCh)

    // listen to websocket connection(s)
    wg.Add(1)
    go pubSocket.PubListen(ctx, &wg, pubCh, testing)

    // connect to data streams. goes through all interval values
    for _, interval := range OHLCinterval {
        wg.Add(1)
        go pubSocket.SubscribeToOHLC(ctx, &wg, pairs, interval)
    }
    // code continues from here but is irrelevant to the data race

}

Custom functions built in WS wrapper:

func ConnectToServer(wg *sync.WaitGroup, server string, testing bool) (*Socket, error) {
    defer wg.Done()

    if server != "public" && server != "private" {
        return nil, errors.New("Server must be 'public' or 'private'")
    }
    var socket Socket
    if server == "public" {
        socket = New(stored_data.Pub_ws_url)
    } else {
        socket = New(stored_data.Priv_ws_url)
    }

    socket.OnConnected = func(socket Socket) {
        log.Println("Connected to server")
    }
    socket.OnTextMessage = func(message string, socket Socket) {
        pubJsonDecoder(message, testing)
        log.Println(message)
    }

    socket.Connect()

    return &socket, nil
}

func (socket *Socket) PubListen(ctx context.Context, wg *sync.WaitGroup, ch chan interface{}, testing bool) {
    defer wg.Done()
    defer socket.Close()
    var res interface{}

    socket.OnTextMessage = func(message string, socket Socket) {
        res = pubJsonDecoder(message, testing)
        ch <- res
    }
    <-ctx.Done()
    log.Println("closing public socket")
    return
}

func (socket *Socket) SubscribeToOHLC(ctx context.Context, wg *sync.WaitGroup, pairs []string, interval int) {
    defer wg.Done()

    sub, _ := json.Marshal(&types.Subscribe{
        Event: "subscribe",
        Subscription: &types.OHLCSubscription{
            Interval: interval,
            Name:     "ohlc",
        },
        Pair: pairs,
    })
    socket.SendBinary(sub)
    <-ctx.Done()
}

Data Race message 1:

==================
WARNING: DATA RACE
Read at 0x00c0001145d8 by goroutine 19:
  kraken_client/ws_client.(*Socket).Connect.func4()
      /Users/grantcanty/go/src/kraken_client/ws_client/websocket_client.go:156 +0x3b5

Previous write at 0x00c0001145d8 by goroutine 20:
  kraken_client/ws_client.(*Socket).PubListen()
      /Users/grantcanty/go/src/kraken_client/ws_client/websocket_wrapper.go:73 +0x234
  main.main.func4()
      /Users/grantcanty/go/src/kraken_client/main.go:79 +0x8d

Goroutine 19 (running) created at:
  kraken_client/ws_client.(*Socket).Connect()
      /Users/grantcanty/go/src/kraken_client/ws_client/websocket_client.go:136 +0xc75
  kraken_client/ws_client.ConnectToServer()
      /Users/grantcanty/go/src/kraken_client/ws_client/websocket_wrapper.go:39 +0x6a4
  main.main()
      /Users/grantcanty/go/src/kraken_client/main.go:59 +0x3d4

Goroutine 20 (running) created at:
  main.main()
      /Users/grantcanty/go/src/kraken_client/main.go:79 +0x6e4
==================

Data race message 2:

==================
WARNING: DATA RACE
Write at 0x00c000300000 by goroutine 19:
  kraken_client/ws_client.(*Socket).PubListen.func1()
      /Users/grantcanty/go/src/kraken_client/ws_client/websocket_wrapper.go:74 +0x84
  kraken_client/ws_client.(*Socket).Connect.func4()
      /Users/grantcanty/go/src/kraken_client/ws_client/websocket_client.go:157 +0x4ab

Previous write at 0x00c000300000 by goroutine 20:
  kraken_client/ws_client.(*Socket).PubListen()
      /Users/grantcanty/go/src/kraken_client/ws_client/websocket_wrapper.go:71 +0x111
  main.main.func4()
      /Users/grantcanty/go/src/kraken_client/main.go:79 +0x8d

Goroutine 19 (running) created at:
  kraken_client/ws_client.(*Socket).Connect()
      /Users/grantcanty/go/src/kraken_client/ws_client/websocket_client.go:136 +0xc75
  kraken_client/ws_client.ConnectToServer()
      /Users/grantcanty/go/src/kraken_client/ws_client/websocket_wrapper.go:39 +0x6a4
  main.main()
      /Users/grantcanty/go/src/kraken_client/main.go:59 +0x3d4

Goroutine 20 (running) created at:
  main.main()
      /Users/grantcanty/go/src/kraken_client/main.go:79 +0x6e4
==================
Grant
  • 431
  • 3
  • 8
  • 1
    From the log, there are multiple goroutines write to the same connection. You may add a mutex for each ws connection, acquire lock before write, release lock after write. – Eric Jul 16 '22 at 06:15
  • Thanks! Didn't realize it was as simple as adding a mutex – Grant Jul 16 '22 at 06:43
  • @Grant where is your `wg.Wait()` ? since there is no `wg.Wait()` the code will keep on running through without waiting the go routine. Hence the `wg.Add(x)` and `wg.Done(x)` does not work as expected. IMHO: - if you really need to async use unbuffered channel instead - since the code still flowing, better to just call the function directly and continue with the logic. – Rajan Jul 16 '22 at 10:35

0 Answers0