Search code examples
gorace-condition

Race condition for not yet existing object


I've a strange race condition. The problem is that it occurs inside an object which is not existing yet.

Here is a demo code:

package main

import (
    //"fmt"
    "time"
)

type Object1 struct {
    A int
    B string
    C []int
    D *Object2
}

type Object2 struct {
    A int
}

func NewObject1() *Object1 {
    return &Object1{
        A: 1,
        B: "abc",
        C: []int{0, 1},
        D: &Object2{},
    }
}

func main() {
    list := []*Object1{}

    tempA := 0
    tempB := ""
    tempC := []int{}
    tempD := &Object2{}

    go func() {
        for {
            for _, object := range list {
                tempA = object.A
                tempB = object.B
                tempC = object.C
                tempD = object.D
            }
        }
    }()

    for {
        list = append(list, NewObject1())

        //fmt.Println("list", list)
        time.Sleep(1 * time.Second)
    }
}

If I run it with the -race flag - I get the warnings:

WARNING: DATA RACE
Read at 0x00c000094040 by goroutine 5:
  main.main.func1()
      /tmp/race.go:39 +0x84

Previous write at 0x00c000094040 by main goroutine:
  main.main()
      /tmp/race.go:21 +0x2a9

Goroutine 5 (running) created at:
  main.main()
      /tmp/race.go:36 +0x276
==================
==================
WARNING: DATA RACE
Read at 0x00c000094048 by goroutine 5:
  main.main.func1()
      /tmp/race.go:40 +0xbe

Previous write at 0x00c000094048 by main goroutine:
  main.main()
      /tmp/race.go:22 +0x2ca

Goroutine 5 (running) created at:
  main.main()
      /tmp/race.go:36 +0x276
==================
==================
WARNING: DATA RACE
Read at 0x00c000094058 by goroutine 5:
  main.main.func1()
      /tmp/race.go:41 +0x118

Previous write at 0x00c000094058 by main goroutine:
  main.main()
      /tmp/race.go:23 +0x341

Goroutine 5 (running) created at:
  main.main()
      /tmp/race.go:36 +0x276
==================
==================
WARNING: DATA RACE
Read at 0x00c000094070 by goroutine 5:
  main.main.func1()
      /tmp/race.go:42 +0x180

Previous write at 0x00c000094070 by main goroutine:
  main.main()
      /tmp/race.go:24 +0x3b8

Goroutine 5 (running) created at:
  main.main()
      /tmp/race.go:36 +0x276
==================

But how is that possible? Reading happens inside a goroutine and writing inside a NewObject1(). 4 errors for every Object1 field. NewObject1() has not created an object yet to append it to the list slice. So list during the reading should be empty or filled with a normal completed objects.

Step by step workflow in my mind:

  1. list is empty;
  2. you start to create new object1;
  3. list is still empty;
  4. you have created a new object and only then add it to the list;
  5. only now list has 1 element;
  6. reading happens.

I don't see a race condition here. If you think differently - please show your own workflow of how things happens.


Solution

  • Race detector detects that you concurrently read and write the same address in memory.

    It is by definition a data race.

    It does not matter when data was actually put to that address (and whether it was put there at all). What only matters is that you access the same memory in different goroutines without synchronisation, and one of those operations is a "write".

    Both are not about Go, but are extremely quality resources:

    1. https://preshing.com/20120710/memory-barriers-are-like-source-control-operations/ - this and basically every other article in that blog is a gem.
    2. http://deadlockempire.github.io/ - a puzzle-like game that reveals nuances of synchronisation and concurrency issues