Skip to content

Commit d5a5a13

Browse files
changkunianlancetaylor
authored andcommitted
sync: clarify the validity to call Map methods inside Range
This change clarifies that calling all Map methods inside the callback of Range is allowed. For further assurance, a nested range call test is also added. Fixes #46399 Change-Id: I0a766a5c1470e6b573ec35df1ccd62b2e46f1561 Reviewed-on: https://go-review.googlesource.com/c/go/+/337389 Reviewed-by: Bryan C. Mills <bcmills@google.com> Run-TryBot: Bryan C. Mills <bcmills@google.com> TryBot-Result: Go Bot <gobot@golang.org> Trust: Ian Lance Taylor <iant@golang.org>
1 parent 3949faf commit d5a5a13

File tree

2 files changed

+53
-2
lines changed

2 files changed

+53
-2
lines changed

src/sync/map.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,8 +311,9 @@ func (e *entry) delete() (value interface{}, ok bool) {
311311
//
312312
// Range does not necessarily correspond to any consistent snapshot of the Map's
313313
// contents: no key will be visited more than once, but if the value for any key
314-
// is stored or deleted concurrently, Range may reflect any mapping for that key
315-
// from any point during the Range call.
314+
// is stored or deleted concurrently (including by f), Range may reflect any
315+
// mapping for that key from any point during the Range call. Range does not
316+
// block other methods on the receiver; even f itself may call any method on m.
316317
//
317318
// Range may be O(N) with the number of elements in the map even if f returns
318319
// false after a constant number of calls.

src/sync/map_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,3 +195,53 @@ func TestIssue40999(t *testing.T) {
195195
runtime.GC()
196196
}
197197
}
198+
199+
func TestMapRangeNestedCall(t *testing.T) { // Issue 46399
200+
var m sync.Map
201+
for i, v := range [3]string{"hello", "world", "Go"} {
202+
m.Store(i, v)
203+
}
204+
m.Range(func(key, value interface{}) bool {
205+
m.Range(func(key, value interface{}) bool {
206+
// We should be able to load the key offered in the Range callback,
207+
// because there are no concurrent Delete involved in this tested map.
208+
if v, ok := m.Load(key); !ok || !reflect.DeepEqual(v, value) {
209+
t.Fatalf("Nested Range loads unexpected value, got %+v want %+v", v, value)
210+
}
211+
212+
// We didn't keep 42 and a value into the map before, if somehow we loaded
213+
// a value from such a key, meaning there must be an internal bug regarding
214+
// nested range in the Map.
215+
if _, loaded := m.LoadOrStore(42, "dummy"); loaded {
216+
t.Fatalf("Nested Range loads unexpected value, want store a new value")
217+
}
218+
219+
// Try to Store then LoadAndDelete the corresponding value with the key
220+
// 42 to the Map. In this case, the key 42 and associated value should be
221+
// removed from the Map. Therefore any future range won't observe key 42
222+
// as we checked in above.
223+
val := "sync.Map"
224+
m.Store(42, val)
225+
if v, loaded := m.LoadAndDelete(42); !loaded || !reflect.DeepEqual(v, val) {
226+
t.Fatalf("Nested Range loads unexpected value, got %v, want %v", v, val)
227+
}
228+
return true
229+
})
230+
231+
// Remove key from Map on-the-fly.
232+
m.Delete(key)
233+
return true
234+
})
235+
236+
// After a Range of Delete, all keys should be removed and any
237+
// further Range won't invoke the callback. Hence length remains 0.
238+
length := 0
239+
m.Range(func(key, value interface{}) bool {
240+
length++
241+
return true
242+
})
243+
244+
if length != 0 {
245+
t.Fatalf("Unexpected sync.Map size, got %v want %v", length, 0)
246+
}
247+
}

0 commit comments

Comments
 (0)