summaryrefslogtreecommitdiff
path: root/src/runtime/proflabel.go
diff options
context:
space:
mode:
authorAustin Clements <austin@google.com>2017-08-17 18:40:07 -0400
committerAustin Clements <austin@google.com>2017-08-18 21:40:37 +0000
commit57584a0ee1761b6204bbd8b48e1129c0217caedc (patch)
tree9c070467f0cbda1faf025b77d4fb90292648f32f /src/runtime/proflabel.go
parentac29f4d01ce6891cad6865eda5b16efed8ef231f (diff)
downloadgo-git-57584a0ee1761b6204bbd8b48e1129c0217caedc.tar.gz
runtime: fix false positive race in profile label reading
Because profile labels are copied from the goroutine into the tag buffer by the signal handler, there's a carefully-crafted set of race detector annotations to create the necessary happens-before edges between setting a goroutine's profile label and retrieving it from the profile tag buffer. Given the constraints of the signal handler, we have to approximate the true synchronization behavior. Currently, that approximation is too weak. Ideally, runtime_setProfLabel would perform a store-release on &getg().labels and copying each label into the profile would perform a load-acquire on &getg().labels. This would create the necessary happens-before edges through each individual g.labels object. Since we can't do this in the signal handler, we instead synchronize on a "labelSync" global. The problem occurs with the following sequence: 1. Goroutine 1 calls setProfLabel, which does a store-release on labelSync. 2. Goroutine 2 calls setProfLabel, which does a store-release on labelSync. 3. Goroutine 3 reads the profile, which does a load-acquire on labelSync. The problem is that the load-acquire only synchronizes with the *most recent* store-release to labelSync, and the two store-releases don't synchronize with each other. So, once goroutine 3 touches the label set by goroutine 1, we report a race. The solution is to use racereleasemerge. This is like a read-modify-write, rather than just a store-release. Each RMW of labelSync in runtime_setProfLabel synchronizes with the previous RMW of labelSync, and this ultimately carries forward to the load-acquire, so it synchronizes with *all* setProfLabel operations, not just the most recent. Change-Id: Iab58329b156122002fff12cfe64fbeacb31c9613 Reviewed-on: https://go-review.googlesource.com/56670 Run-TryBot: Austin Clements <austin@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Russ Cox <rsc@golang.org> Reviewed-by: Dmitry Vyukov <dvyukov@google.com>
Diffstat (limited to 'src/runtime/proflabel.go')
-rw-r--r--src/runtime/proflabel.go17
1 files changed, 16 insertions, 1 deletions
diff --git a/src/runtime/proflabel.go b/src/runtime/proflabel.go
index 1b41a8a16e..b2a161729e 100644
--- a/src/runtime/proflabel.go
+++ b/src/runtime/proflabel.go
@@ -13,8 +13,23 @@ func runtime_setProfLabel(labels unsafe.Pointer) {
// Introduce race edge for read-back via profile.
// This would more properly use &getg().labels as the sync address,
// but we do the read in a signal handler and can't call the race runtime then.
+ //
+ // This uses racereleasemerge rather than just racerelease so
+ // the acquire in profBuf.read synchronizes with *all* prior
+ // setProfLabel operations, not just the most recent one. This
+ // is important because profBuf.read will observe different
+ // labels set by different setProfLabel operations on
+ // different goroutines, so it needs to synchronize with all
+ // of them (this wouldn't be an issue if we could synchronize
+ // on &getg().labels since we would synchronize with each
+ // most-recent labels write separately.)
+ //
+ // racereleasemerge is like a full read-modify-write on
+ // labelSync, rather than just a store-release, so it carries
+ // a dependency on the previous racereleasemerge, which
+ // ultimately carries forward to the acquire in profBuf.read.
if raceenabled {
- racerelease(unsafe.Pointer(&labelSync))
+ racereleasemerge(unsafe.Pointer(&labelSync))
}
getg().labels = labels
}