Skip to content

Commit 231f290

Browse files
cuonglmgopherbot
authored andcommitted
runtime: mark map bucket slots as empty during map clear
So iterators that are in progress can know entries have been deleted and terminate the iterator properly. Update #55002 Update #56351 Fixes #59411 Change-Id: I924f16a00fe4ed6564f730a677348a6011d3fb67 Reviewed-on: https://go-review.googlesource.com/c/go/+/481935 Reviewed-by: Keith Randall <[email protected]> Auto-Submit: Cuong Manh Le <[email protected]> Run-TryBot: Cuong Manh Le <[email protected]> Reviewed-by: Michael Knyszek <[email protected]> Reviewed-by: Keith Randall <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 66cac9e commit 231f290

File tree

2 files changed

+93
-0
lines changed

2 files changed

+93
-0
lines changed

src/runtime/map.go

+16
Original file line numberDiff line numberDiff line change
@@ -1008,6 +1008,22 @@ func mapclear(t *maptype, h *hmap) {
10081008

10091009
h.flags ^= hashWriting
10101010

1011+
// Mark buckets empty, so existing iterators can be terminated, see issue #59411.
1012+
markBucketsEmpty := func(bucket unsafe.Pointer, mask uintptr) {
1013+
for i := uintptr(0); i <= mask; i++ {
1014+
b := (*bmap)(add(bucket, i*uintptr(t.bucketsize)))
1015+
for ; b != nil; b = b.overflow(t) {
1016+
for i := uintptr(0); i < bucketCnt; i++ {
1017+
b.tophash[i] = emptyRest
1018+
}
1019+
}
1020+
}
1021+
}
1022+
markBucketsEmpty(h.buckets, bucketMask(h.B))
1023+
if oldBuckets := h.oldbuckets; oldBuckets != nil {
1024+
markBucketsEmpty(oldBuckets, h.oldbucketmask())
1025+
}
1026+
10111027
h.flags &^= sameSizeGrow
10121028
h.oldbuckets = nil
10131029
h.nevacuate = 0

test/fixedbugs/issue59411.go

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// run
2+
3+
// Copyright 2023 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
package main
8+
9+
import (
10+
"math"
11+
"reflect"
12+
)
13+
14+
func main() {
15+
for i := 0; i < 100; i++ {
16+
f()
17+
g()
18+
}
19+
}
20+
21+
func f() {
22+
// Allocate map.
23+
m := map[float64]int{}
24+
// Fill to just before a growth trigger.
25+
const N = 13 << 4 // 6.5 * 2 * 2^k
26+
for i := 0; i < N; i++ {
27+
m[math.NaN()] = i
28+
}
29+
// Trigger growth.
30+
m[math.NaN()] = N
31+
32+
// Iterate through map.
33+
i := 0
34+
for range m {
35+
if i == 6 {
36+
// Partway through iteration, clear the map.
37+
clear(m)
38+
} else if i > 6 {
39+
// If we advance to the next iteration, that's a bug.
40+
panic("BAD")
41+
}
42+
i++
43+
}
44+
if len(m) != 0 {
45+
panic("clear did not empty the map")
46+
}
47+
}
48+
49+
func g() {
50+
// Allocate map.
51+
m := map[float64]int{}
52+
// Fill to just before a growth trigger.
53+
const N = 13 << 4 // 6.5 * 2 * 2^k
54+
for i := 0; i < N; i++ {
55+
m[math.NaN()] = i
56+
}
57+
// Trigger growth.
58+
m[math.NaN()] = N
59+
60+
// Iterate through map.
61+
i := 0
62+
v := reflect.ValueOf(m)
63+
iter := v.MapRange()
64+
for iter.Next() {
65+
if i == 6 {
66+
// Partway through iteration, clear the map.
67+
v.Clear()
68+
} else if i > 6 {
69+
// If we advance to the next iteration, that's a bug.
70+
panic("BAD")
71+
}
72+
i++
73+
}
74+
if v.Len() != 0 {
75+
panic("clear did not empty the map")
76+
}
77+
}

0 commit comments

Comments
 (0)