Skip to content

Commit 50c5042

Browse files
committed
runtime: best-effort detection of concurrent misuse of maps
If reports like golang#13062 are really concurrent misuse of maps, we can detect that, at least some of the time, with a cheap check. There is an extra pair of memory writes for writing to a map, but to the same cache line as h.count, which is often being modified anyway, and there is an extra memory read for reading from a map, but to the same cache line as h.count, which is always being read anyway. So the check should be basically invisible and may help reduce the number of "mysterious runtime crash due to map misuse" reports. Change-Id: I0e71b0d92eaa3b7bef48bf41b0f5ab790092487e Reviewed-on: https://go-review.googlesource.com/17501 Reviewed-by: Keith Randall <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]> Reviewed-by: Austin Clements <[email protected]>
1 parent 7ebb96a commit 50c5042

File tree

2 files changed

+52
-3
lines changed

2 files changed

+52
-3
lines changed

src/runtime/hashmap.go

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ const (
9595
// flags
9696
iterator = 1 // there may be an iterator using buckets
9797
oldIterator = 2 // there may be an iterator using oldbuckets
98+
hashWriting = 4 // a goroutine is writing to the map
9899

99100
// sentinel bucket ID for iterator checks
100101
noCheck = 1<<(8*sys.PtrSize) - 1
@@ -284,6 +285,9 @@ func mapaccess1(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
284285
if h == nil || h.count == 0 {
285286
return atomic.Loadp(unsafe.Pointer(&zeroptr))
286287
}
288+
if h.flags&hashWriting != 0 {
289+
throw("concurrent map read and map write")
290+
}
287291
alg := t.key.alg
288292
hash := alg.hash(key, uintptr(h.hash0))
289293
m := uintptr(1)<<h.B - 1
@@ -335,6 +339,9 @@ func mapaccess2(t *maptype, h *hmap, key unsafe.Pointer) (unsafe.Pointer, bool)
335339
if h == nil || h.count == 0 {
336340
return atomic.Loadp(unsafe.Pointer(&zeroptr)), false
337341
}
342+
if h.flags&hashWriting != 0 {
343+
throw("concurrent map read and map write")
344+
}
338345
alg := t.key.alg
339346
hash := alg.hash(key, uintptr(h.hash0))
340347
m := uintptr(1)<<h.B - 1
@@ -378,6 +385,9 @@ func mapaccessK(t *maptype, h *hmap, key unsafe.Pointer) (unsafe.Pointer, unsafe
378385
if h == nil || h.count == 0 {
379386
return nil, nil
380387
}
388+
if h.flags&hashWriting != 0 {
389+
throw("concurrent map read and map write")
390+
}
381391
alg := t.key.alg
382392
hash := alg.hash(key, uintptr(h.hash0))
383393
m := uintptr(1)<<h.B - 1
@@ -431,6 +441,10 @@ func mapassign1(t *maptype, h *hmap, key unsafe.Pointer, val unsafe.Pointer) {
431441
msanread(key, t.key.size)
432442
msanread(val, t.elem.size)
433443
}
444+
if h.flags&hashWriting != 0 {
445+
throw("concurrent map writes")
446+
}
447+
h.flags |= hashWriting
434448

435449
alg := t.key.alg
436450
hash := alg.hash(key, uintptr(h.hash0))
@@ -481,7 +495,7 @@ again:
481495
v2 = *((*unsafe.Pointer)(v2))
482496
}
483497
typedmemmove(t.elem, v2, val)
484-
return
498+
goto done
485499
}
486500
ovf := b.overflow(t)
487501
if ovf == nil {
@@ -520,6 +534,12 @@ again:
520534
typedmemmove(t.elem, insertv, val)
521535
*inserti = top
522536
h.count++
537+
538+
done:
539+
if h.flags&hashWriting == 0 {
540+
throw("concurrent map writes")
541+
}
542+
h.flags &^= hashWriting
523543
}
524544

525545
func mapdelete(t *maptype, h *hmap, key unsafe.Pointer) {
@@ -535,6 +555,11 @@ func mapdelete(t *maptype, h *hmap, key unsafe.Pointer) {
535555
if h == nil || h.count == 0 {
536556
return
537557
}
558+
if h.flags&hashWriting != 0 {
559+
throw("concurrent map writes")
560+
}
561+
h.flags |= hashWriting
562+
538563
alg := t.key.alg
539564
hash := alg.hash(key, uintptr(h.hash0))
540565
bucket := hash & (uintptr(1)<<h.B - 1)
@@ -564,13 +589,19 @@ func mapdelete(t *maptype, h *hmap, key unsafe.Pointer) {
564589
memclr(v, uintptr(t.valuesize))
565590
b.tophash[i] = empty
566591
h.count--
567-
return
592+
goto done
568593
}
569594
b = b.overflow(t)
570595
if b == nil {
571-
return
596+
goto done
572597
}
573598
}
599+
600+
done:
601+
if h.flags&hashWriting == 0 {
602+
throw("concurrent map writes")
603+
}
604+
h.flags &^= hashWriting
574605
}
575606

576607
func mapiterinit(t *maptype, h *hmap, it *hiter) {

src/runtime/hashmap_fast.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ func mapaccess1_fast32(t *maptype, h *hmap, key uint32) unsafe.Pointer {
1818
if h == nil || h.count == 0 {
1919
return atomic.Loadp(unsafe.Pointer(&zeroptr))
2020
}
21+
if h.flags&hashWriting != 0 {
22+
throw("concurrent map read and map write")
23+
}
2124
var b *bmap
2225
if h.B == 0 {
2326
// One-bucket table. No need to hash.
@@ -60,6 +63,9 @@ func mapaccess2_fast32(t *maptype, h *hmap, key uint32) (unsafe.Pointer, bool) {
6063
if h == nil || h.count == 0 {
6164
return atomic.Loadp(unsafe.Pointer(&zeroptr)), false
6265
}
66+
if h.flags&hashWriting != 0 {
67+
throw("concurrent map read and map write")
68+
}
6369
var b *bmap
6470
if h.B == 0 {
6571
// One-bucket table. No need to hash.
@@ -102,6 +108,9 @@ func mapaccess1_fast64(t *maptype, h *hmap, key uint64) unsafe.Pointer {
102108
if h == nil || h.count == 0 {
103109
return atomic.Loadp(unsafe.Pointer(&zeroptr))
104110
}
111+
if h.flags&hashWriting != 0 {
112+
throw("concurrent map read and map write")
113+
}
105114
var b *bmap
106115
if h.B == 0 {
107116
// One-bucket table. No need to hash.
@@ -144,6 +153,9 @@ func mapaccess2_fast64(t *maptype, h *hmap, key uint64) (unsafe.Pointer, bool) {
144153
if h == nil || h.count == 0 {
145154
return atomic.Loadp(unsafe.Pointer(&zeroptr)), false
146155
}
156+
if h.flags&hashWriting != 0 {
157+
throw("concurrent map read and map write")
158+
}
147159
var b *bmap
148160
if h.B == 0 {
149161
// One-bucket table. No need to hash.
@@ -186,6 +198,9 @@ func mapaccess1_faststr(t *maptype, h *hmap, ky string) unsafe.Pointer {
186198
if h == nil || h.count == 0 {
187199
return atomic.Loadp(unsafe.Pointer(&zeroptr))
188200
}
201+
if h.flags&hashWriting != 0 {
202+
throw("concurrent map read and map write")
203+
}
189204
key := stringStructOf(&ky)
190205
if h.B == 0 {
191206
// One-bucket table.
@@ -288,6 +303,9 @@ func mapaccess2_faststr(t *maptype, h *hmap, ky string) (unsafe.Pointer, bool) {
288303
if h == nil || h.count == 0 {
289304
return atomic.Loadp(unsafe.Pointer(&zeroptr)), false
290305
}
306+
if h.flags&hashWriting != 0 {
307+
throw("concurrent map read and map write")
308+
}
291309
key := stringStructOf(&ky)
292310
if h.B == 0 {
293311
// One-bucket table.

0 commit comments

Comments
 (0)