func TestGCTestMoveStackOnNextCall(t *testing.T) {
t.Parallel()
var onStack int
- runtime.GCTestMoveStackOnNextCall()
- moveStackCheck(t, &onStack, uintptr(unsafe.Pointer(&onStack)))
+ // GCTestMoveStackOnNextCall can fail in rare cases if there's
+ // a preemption. This won't happen many times in quick
+ // succession, so just retry a few times.
+ for retry := 0; retry < 5; retry++ {
+ runtime.GCTestMoveStackOnNextCall()
+ if moveStackCheck(t, &onStack, uintptr(unsafe.Pointer(&onStack))) {
+ // Passed.
+ return
+ }
+ }
+ t.Fatal("stack did not move")
}
// This must not be inlined because the point is to force a stack
// growth check and move the stack.
//
//go:noinline
-func moveStackCheck(t *testing.T, new *int, old uintptr) {
+func moveStackCheck(t *testing.T, new *int, old uintptr) bool {
// new should have been updated by the stack move;
// old should not have.
t.Fatalf("test bug: new (%#x) should be a stack pointer, not %s", new2, cls)
}
// This was a real failure.
- t.Fatal("stack did not move")
+ return false
}
+ return true
}
func TestGCTestIsReachable(t *testing.T) {
// if any other work appears after this call (such as returning).
// Typically the following call should be marked go:noinline so it
// performs a stack check.
+//
+// In rare cases this may not cause the stack to move, specifically if
+// there's a preemption between this call and the next.
func gcTestMoveStackOnNextCall() {
gp := getg()
gp.stackguard0 = getcallersp()