Skip to content

Commit d6bf2d7

Browse files
committed
runtime: test memmove writes pointers atomically
In the previous CL we ensures that memmove writes pointers atomically, so the concurrent GC won't observe a partially updated pointer. This CL adds a test. Change-Id: Icd1124bf3a15ef25bac20c7fb8933f1a642d897c Reviewed-on: https://go-review.googlesource.com/c/go/+/212627 Reviewed-by: Austin Clements <[email protected]> Run-TryBot: Cherry Zhang <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent 822094f commit d6bf2d7

File tree

2 files changed

+70
-0
lines changed

2 files changed

+70
-0
lines changed

src/runtime/export_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ var NetpollGenericInit = netpollGenericInit
4545

4646
var ParseRelease = parseRelease
4747

48+
var Memmove = memmove
49+
var MemclrNoHeapPointers = memclrNoHeapPointers
50+
4851
const PreemptMSupported = preemptMSupported
4952

5053
type LFNode struct {

src/runtime/memmove_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import (
1111
"internal/race"
1212
"internal/testenv"
1313
. "runtime"
14+
"sync/atomic"
1415
"testing"
16+
"unsafe"
1517
)
1618

1719
func TestMemmove(t *testing.T) {
@@ -206,6 +208,71 @@ func cmpb(a, b []byte) int {
206208
return l
207209
}
208210

211+
// Ensure that memmove writes pointers atomically, so the GC won't
212+
// observe a partially updated pointer.
213+
func TestMemmoveAtomicity(t *testing.T) {
214+
if race.Enabled {
215+
t.Skip("skip under the race detector -- this test is intentionally racy")
216+
}
217+
218+
var x int
219+
220+
for _, backward := range []bool{true, false} {
221+
for _, n := range []int{3, 4, 5, 6, 7, 8, 9, 10, 15, 25, 49} {
222+
n := n
223+
224+
// test copying [N]*int.
225+
sz := uintptr(n * PtrSize)
226+
name := fmt.Sprint(sz)
227+
if backward {
228+
name += "-backward"
229+
} else {
230+
name += "-forward"
231+
}
232+
t.Run(name, func(t *testing.T) {
233+
// Use overlapping src and dst to force forward/backward copy.
234+
var s [100]*int
235+
src := s[n-1 : 2*n-1]
236+
dst := s[:n]
237+
if backward {
238+
src, dst = dst, src
239+
}
240+
for i := range src {
241+
src[i] = &x
242+
}
243+
for i := range dst {
244+
dst[i] = nil
245+
}
246+
247+
var ready uint32
248+
go func() {
249+
sp := unsafe.Pointer(&src[0])
250+
dp := unsafe.Pointer(&dst[0])
251+
atomic.StoreUint32(&ready, 1)
252+
for i := 0; i < 10000; i++ {
253+
Memmove(dp, sp, sz)
254+
MemclrNoHeapPointers(dp, sz)
255+
}
256+
atomic.StoreUint32(&ready, 2)
257+
}()
258+
259+
for atomic.LoadUint32(&ready) == 0 {
260+
Gosched()
261+
}
262+
263+
for atomic.LoadUint32(&ready) != 2 {
264+
for i := range dst {
265+
p := dst[i]
266+
if p != nil && p != &x {
267+
t.Fatalf("got partially updated pointer %p at dst[%d], want either nil or %p", p, i, &x)
268+
}
269+
}
270+
}
271+
})
272+
}
273+
}
274+
}
275+
209276
func benchmarkSizes(b *testing.B, sizes []int, fn func(b *testing.B, n int)) {
210277
for _, n := range sizes {
211278
b.Run(fmt.Sprint(n), func(b *testing.B) {

0 commit comments

Comments
 (0)