Seven Story Rabbit Hole

Sometimes awesome things happen in deep rabbit holes. Or not.

   images

Go Race Detector Gotcha With Value Receivers

I ran into the following race detector error:

1
2
3
4
5
6
7
8
9
10
11
12
WARNING: DATA RACE
Write by goroutine 44:
  github.com/couchbaselabs/sg-replicate.stateFnActiveFetchCheckpoint()
      /Users/tleyden/Development/gocode/src/github.com/couchbaselabs/sg-replicate/replication_state.go:53 +0xb1d
  github.com/couchbaselabs/sg-replicate.(*Replication).processEvents()
      /Users/tleyden/Development/gocode/src/github.com/couchbaselabs/sg-replicate/synctube.go:120 +0xa3

Previous read by goroutine 27:
  github.com/couchbaselabs/sg-replicate.(*Replication).GetStats()
      <autogenerated>:24 +0xef
  github.com/couchbase/sync_gateway/base.(*Replicator).populateActiveTaskFromReplication()
      /Users/tleyden/Development/gocode/src/github.com/couchbase/sync_gateway/base/replicator.go:241 +0x145

Goroutine 44 was running this code:

1
2
3
func (r *Replication) shutdownEventChannel() {
  r.EventChan = nil
}

and nil’ing out the r.EventChan field.

While goroutine 27 was calling this code on the same *Replication instance:

1
2
3
func (r Replication) GetStats() ReplicationStats {
  return r.Stats
}

It didn’t make sense, because they were accessing different fields of the Replication — one was writing to r.EventChan while the other was reading from r.Stats.

Then I changed the GetStats() method to this:

1
2
3
func (r Replication) GetStats() ReplicationStats {
  return ReplicationStats{}
}

and it still failed!

I started wandering around the Couchbase office looking for help, and got Steve Yen to help me.

He was asking me about using a pointer receiver vs a value receiver here, and then we realized that by using a value reciever it was copying all the fields, and therefore reading all of the fields, including the r.EventChan field that the other goroutine was concurrently writing to! Hence, the data race that was subtly caused by using a value receiver..

The fix was to convert this over to a pointer reciever, and the data race disappeared!

1
2
3
func (r *Replication) GetStats() ReplicationStats {
     return r.Stats
}

Comments