Commit b6bfc92d authored by Dmitriy Vyukov's avatar Dmitriy Vyukov

runtime: fix race on hashmap flags field

Use atomic operations on flags field to make sure we aren't
losing a flag update during parallel map operations.

R=golang-dev, dave, r
CC=golang-dev
https://golang.org/cl/8377046
parent f4de042e
...@@ -95,8 +95,8 @@ struct Bucket ...@@ -95,8 +95,8 @@ struct Bucket
struct Hmap struct Hmap
{ {
uintgo count; // # live cells == size of map. Must be first (used by len() builtin) uintgo count; // # live cells == size of map. Must be first (used by len() builtin)
uint32 flags;
uint8 B; // log_2 of # of buckets (can hold up to LOAD * 2^B items) uint8 B; // log_2 of # of buckets (can hold up to LOAD * 2^B items)
uint8 flags;
uint8 keysize; // key size in bytes uint8 keysize; // key size in bytes
uint8 valuesize; // value size in bytes uint8 valuesize; // value size in bytes
uint16 bucketsize; // bucket size in bytes uint16 bucketsize; // bucket size in bytes
...@@ -767,6 +767,8 @@ struct hash_iter ...@@ -767,6 +767,8 @@ struct hash_iter
static void static void
hash_iter_init(MapType *t, Hmap *h, struct hash_iter *it) hash_iter_init(MapType *t, Hmap *h, struct hash_iter *it)
{ {
uint32 old;
if(sizeof(struct hash_iter) / sizeof(uintptr) != 11) { if(sizeof(struct hash_iter) / sizeof(uintptr) != 11) {
runtime·throw("hash_iter size incorrect"); // see ../../cmd/gc/range.c runtime·throw("hash_iter size incorrect"); // see ../../cmd/gc/range.c
} }
...@@ -783,7 +785,14 @@ hash_iter_init(MapType *t, Hmap *h, struct hash_iter *it) ...@@ -783,7 +785,14 @@ hash_iter_init(MapType *t, Hmap *h, struct hash_iter *it)
it->bptr = nil; it->bptr = nil;
// Remember we have an iterator. // Remember we have an iterator.
h->flags |= Iterator | OldIterator; // careful: see issue 5120. // Can run concurrently with another hash_iter_init() and with reflect·mapiterinit().
for(;;) {
old = h->flags;
if((old&(Iterator|OldIterator)) == (Iterator|OldIterator))
break;
if(runtime·cas(&h->flags, old, old|Iterator|OldIterator))
break;
}
if(h->buckets == nil) { if(h->buckets == nil) {
// Empty map. Force next hash_next to exit without // Empty map. Force next hash_next to exit without
...@@ -1370,18 +1379,24 @@ runtime·mapiterinit(MapType *t, Hmap *h, struct hash_iter *it) ...@@ -1370,18 +1379,24 @@ runtime·mapiterinit(MapType *t, Hmap *h, struct hash_iter *it)
void void
reflect·mapiterinit(MapType *t, Hmap *h, struct hash_iter *it) reflect·mapiterinit(MapType *t, Hmap *h, struct hash_iter *it)
{ {
uint8 flags; uint32 old, new;
if(h != nil && t->key->size > sizeof(void*)) { if(h != nil && t->key->size > sizeof(void*)) {
// reflect·mapiterkey returns pointers to key data, // reflect·mapiterkey returns pointers to key data,
// and reflect holds them, so we cannot free key data // and reflect holds them, so we cannot free key data
// eagerly anymore. // eagerly anymore.
flags = h->flags; // Can run concurrently with another reflect·mapiterinit() and with hash_iter_init().
if(flags & IndirectKey) for(;;) {
flags &= ~CanFreeKey; old = h->flags;
if(old & IndirectKey)
new = old & ~CanFreeKey;
else else
flags &= ~CanFreeBucket; new = old & ~CanFreeBucket;
h->flags = flags; if(new == old)
break;
if(runtime·cas(&h->flags, old, new))
break;
}
} }
it = runtime·mal(sizeof *it); it = runtime·mal(sizeof *it);
......
...@@ -7,7 +7,7 @@ package runtime_test ...@@ -7,7 +7,7 @@ package runtime_test
import ( import (
"fmt" "fmt"
"math" "math"
"os" "reflect"
"runtime" "runtime"
"sort" "sort"
"strings" "strings"
...@@ -234,8 +234,8 @@ func TestIterGrowWithGC(t *testing.T) { ...@@ -234,8 +234,8 @@ func TestIterGrowWithGC(t *testing.T) {
} }
} }
func TestConcurrentReadsAfterGrowth(t *testing.T) { func testConcurrentReadsAfterGrowth(t *testing.T, useReflect bool) {
if os.Getenv("GOMAXPROCS") == "" { if runtime.GOMAXPROCS(-1) == 1 {
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(16)) defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(16))
} }
numLoop := 10 numLoop := 10
...@@ -262,12 +262,31 @@ func TestConcurrentReadsAfterGrowth(t *testing.T) { ...@@ -262,12 +262,31 @@ func TestConcurrentReadsAfterGrowth(t *testing.T) {
_ = m[key] _ = m[key]
} }
}() }()
if useReflect {
wg.Add(1)
go func() {
defer wg.Done()
mv := reflect.ValueOf(m)
keys := mv.MapKeys()
for _, k := range keys {
mv.MapIndex(k)
}
}()
}
} }
wg.Wait() wg.Wait()
} }
} }
} }
func TestConcurrentReadsAfterGrowth(t *testing.T) {
testConcurrentReadsAfterGrowth(t, false)
}
func TestConcurrentReadsAfterGrowthReflect(t *testing.T) {
testConcurrentReadsAfterGrowth(t, true)
}
func TestBigItems(t *testing.T) { func TestBigItems(t *testing.T) {
var key [256]string var key [256]string
for i := 0; i < 256; i++ { for i := 0; i < 256; i++ {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment