diff --git a/src/runtime/gc_test.go b/src/runtime/gc_test.go index 1ea1c2c745a..273f399864b 100644 --- a/src/runtime/gc_test.go +++ b/src/runtime/gc_test.go @@ -205,15 +205,24 @@ func TestGcZombieReporting(t *testing.T) { 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. @@ -228,8 +237,9 @@ func moveStackCheck(t *testing.T, new *int, old uintptr) { 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) { diff --git a/src/runtime/mgc.go b/src/runtime/mgc.go index ecac354d833..f0c03b51028 100644 --- a/src/runtime/mgc.go +++ b/src/runtime/mgc.go @@ -2347,6 +2347,9 @@ func fmtNSAsMS(buf []byte, ns uint64) []byte { // 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()