Skip to content

Instantly share code, notes, and snippets.

@Grabber
Last active December 6, 2023 23:54
Show Gist options
  • Save Grabber/556674fa180c98f4062bd576476d98b0 to your computer and use it in GitHub Desktop.
Save Grabber/556674fa180c98f4062bd576476d98b0 to your computer and use it in GitHub Desktop.
Golang / sync.WaitGroup / data race investigation with nested structs
package main
import (
"fmt"
"sync"
"time"
)
type Session struct {
Id string
wg *sync.WaitGroup
}
type ClientMessage struct {
Sess *Session
}
func main() {
doneCh := make(chan any)
sess := Session{
Id: "OoOoOoOo",
wg: new(sync.WaitGroup),
}
sess.wg.Add(1)
go func(sess *Session) {
for i := 0; i < 3; i++ {
fmt.Printf("go func(sess *Session): %v -> %d\n", sess, i)
time.Sleep(500 * time.Millisecond)
}
sess.wg.Done()
}(&sess)
cm := ClientMessage{
Sess: &sess,
}
sess.wg.Add(1)
go func(cm *ClientMessage) {
for i := 0; i < 10; i++ {
fmt.Println("loop")
fmt.Printf("go func(cm *ClientMessage): %v -> %d\n", cm.Sess, i)
fmt.Printf("go func(cm *ClientMessage): %v -> %d\n", cm.Sess.Id, i)
time.Sleep(1 * time.Second)
}
cm.Sess.wg.Done()
}(&cm)
cmb := ClientMessage{
Sess: &sess,
}
sess.wg.Add(1)
go func(cm *ClientMessage) {
for i := 0; i < 10; i++ {
fmt.Printf("go func(cm *ClientMessage): %d\n", i)
fmt.Printf("go func(cm *ClientMessage): %v -> %d\n", cm.Sess, i)
time.Sleep(1 * time.Second)
}
cm.Sess.wg.Done()
}(&cmb)
go func(cm *ClientMessage) {
fmt.Println("func(cm *ClientMessage),1")
cm.Sess.wg.Wait()
fmt.Println("func(cm *ClientMessage),2")
doneCh <- struct{}{}
fmt.Println("func(cm *ClientMessage),3")
}(&cm)
fmt.Println("Done,1")
<-doneCh
fmt.Println("Done,3")
}
@Grabber
Copy link
Author

Grabber commented Dec 5, 2023

This is the output when using non-pointer: wg sync.WaitGroup

lvmc@Luizs-MacBook-Pro x % ./main 
Done,1
go func(sess *Session): 0
func(cm *ClientMessage),1
go func(cm *ClientMessage): 0
==================
WARNING: DATA RACE
Read at 0x00c000126018 by goroutine 7:
  reflect.typedmemmove()
      /usr/local/go/src/runtime/mbarrier.go:193 +0x0
  reflect.packEface()
      /usr/local/go/src/reflect/value.go:135 +0xc5
  reflect.valueInterface()
      /usr/local/go/src/reflect/value.go:1520 +0x179
  reflect.Value.Interface()
      /usr/local/go/src/reflect/value.go:1490 +0xc8
  fmt.(*pp).printValue()
      /usr/local/go/src/fmt/print.go:769 +0x98
  fmt.(*pp).printValue()
      /usr/local/go/src/fmt/print.go:923 +0x134a
  fmt.(*pp).printArg()
      /usr/local/go/src/fmt/print.go:759 +0xda4
  fmt.(*pp).doPrintf()
      /usr/local/go/src/fmt/print.go:1077 +0x58f
  fmt.Fprintf()
      /usr/local/go/src/fmt/print.go:224 +0x78
  fmt.Printf()
      /usr/local/go/src/fmt/print.go:233 +0xc4
  main.main.func2()
      /tmp/x/main.go:43 +0x31
  main.main.func6()
      /tmp/x/main.go:47 +0x41

Previous write at 0x00c000126018 by goroutine 9:
  runtime.racewrite()
      <autogenerated>:1 +0x1e
  main.main.func4()
      /tmp/x/main.go:65 +0x9b
  main.main.func8()
      /tmp/x/main.go:69 +0x41

Goroutine 7 (running) created at:
  main.main()
      /tmp/x/main.go:40 +0x244

Goroutine 9 (running) created at:
  main.main()
      /tmp/x/main.go:63 +0x448
==================
go func(cm *ClientMessage): &{OoOoOoOo {{} {{} {} 12884901889} 0}} -> 0
go func(sess *Session): 1
go func(cm *ClientMessage): 1
go func(cm *ClientMessage): &{OoOoOoOo {{} {{} {} 12884901889} 0}} -> 1
go func(sess *Session): 2
go func(cm *ClientMessage): 2
go func(cm *ClientMessage): &{OoOoOoOo {{} {{} {} 12884901889} 0}} -> 2
go func(cm *ClientMessage): 3
go func(sess *Session): 3
go func(cm *ClientMessage): &{OoOoOoOo {{} {{} {} 12884901889} 0}} -> 3
go func(sess *Session): 4
go func(cm *ClientMessage): 4
go func(cm *ClientMessage): &{OoOoOoOo {{} {{} {} 12884901889} 0}} -> 4
go func(cm *ClientMessage): 5
go func(sess *Session): 5
go func(cm *ClientMessage): &{OoOoOoOo {{} {{} {} 12884901889} 0}} -> 5
go func(sess *Session): 6
go func(cm *ClientMessage): 6
go func(cm *ClientMessage): &{OoOoOoOo {{} {{} {} 12884901889} 0}} -> 6
go func(cm *ClientMessage): 7
go func(sess *Session): 7
go func(cm *ClientMessage): &{OoOoOoOo {{} {{} {} 12884901889} 0}} -> 7
go func(sess *Session): 8
go func(cm *ClientMessage): 8
go func(cm *ClientMessage): &{OoOoOoOo {{} {{} {} 12884901889} 0}} -> 8
go func(cm *ClientMessage): 9
go func(sess *Session): 9
go func(cm *ClientMessage): &{OoOoOoOo {{} {{} {} 12884901889} 0}} -> 9
func(cm *ClientMessage),2
func(cm *ClientMessage),3
Done,3
Found 1 data race(s)

@Grabber
Copy link
Author

Grabber commented Dec 6, 2023

I found a very interesting data race when using a sync.WaitGroup implace to a nested struct with some gorountines concurrently calling .Done() while another goroutine is waiting on .Wait(). To avoid the race condition, wg must be a pointer and be explicity initialized with new(sync.WaitGroup).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment