Skip to content

Commit 79fd633

Browse files
mknyszekgopherbot
authored andcommitted
internal/weak: shade pointer in weak-to-strong conversion
There's a bug in the weak-to-strong conversion in that creating the *only* strong pointer to some weakly-held object during the mark phase may result in that object not being properly marked. The exact mechanism for this is that the new strong pointer will always point to a white object (because it was only weakly referenced up until this point) and it can then be stored in a blackened stack, hiding it from the garbage collector. This "hide a white pointer in the stack" problem is pretty much exactly what the Yuasa part of the hybrid write barrier is trying to catch, so we need to do the same thing the write barrier would do: shade the pointer. Added a test and confirmed that it fails with high probability if the pointer shading is missing. Fixes #69210. Change-Id: Iaae64ae95ea7e975c2f2c3d4d1960e74e1bd1c3f Reviewed-on: https://go-review.googlesource.com/c/go/+/610396 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Cherry Mui <[email protected]> Reviewed-by: Michael Pratt <[email protected]> Auto-Submit: Michael Knyszek <[email protected]>
1 parent 1b4cf43 commit 79fd633

File tree

2 files changed

+108
-1
lines changed

2 files changed

+108
-1
lines changed

src/internal/weak/pointer_test.go

+82
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55
package weak_test
66

77
import (
8+
"context"
89
"internal/weak"
910
"runtime"
11+
"sync"
1012
"testing"
13+
"time"
1114
)
1215

1316
type T struct {
@@ -128,3 +131,82 @@ func TestPointerFinalizer(t *testing.T) {
128131
t.Errorf("weak pointer is non-nil even after finalization: %v", wt)
129132
}
130133
}
134+
135+
// Regression test for issue 69210.
136+
//
137+
// Weak-to-strong conversions must shade the new strong pointer, otherwise
138+
// that might be creating the only strong pointer to a white object which
139+
// is hidden in a blackened stack.
140+
//
141+
// Never fails if correct, fails with some high probability if incorrect.
142+
func TestIssue69210(t *testing.T) {
143+
if testing.Short() {
144+
t.Skip("this is a stress test that takes seconds to run on its own")
145+
}
146+
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
147+
defer cancel()
148+
149+
// What we're trying to do is manufacture the conditions under which this
150+
// bug happens. Specifically, we want:
151+
//
152+
// 1. To create a whole bunch of objects that are only weakly-pointed-to,
153+
// 2. To call Strong while the GC is in the mark phase,
154+
// 3. The new strong pointer to be missed by the GC,
155+
// 4. The following GC cycle to mark a free object.
156+
//
157+
// Unfortunately, (2) and (3) are hard to control, but we can increase
158+
// the likelihood by having several goroutines do (1) at once while
159+
// another goroutine constantly keeps us in the GC with runtime.GC.
160+
// Like throwing darts at a dart board until they land just right.
161+
// We can increase the likelihood of (4) by adding some delay after
162+
// creating the strong pointer, but only if it's non-nil. If it's nil,
163+
// that means it was already collected in which case there's no chance
164+
// of triggering the bug, so we want to retry as fast as possible.
165+
// Our heap here is tiny, so the GCs will go by fast.
166+
//
167+
// As of 2024-09-03, removing the line that shades pointers during
168+
// the weak-to-strong conversion causes this test to fail about 50%
169+
// of the time.
170+
171+
var wg sync.WaitGroup
172+
wg.Add(1)
173+
go func() {
174+
defer wg.Done()
175+
for {
176+
runtime.GC()
177+
178+
select {
179+
case <-ctx.Done():
180+
return
181+
default:
182+
}
183+
}
184+
}()
185+
for range max(runtime.GOMAXPROCS(-1)-1, 1) {
186+
wg.Add(1)
187+
go func() {
188+
defer wg.Done()
189+
for {
190+
for range 5 {
191+
bt := new(T)
192+
wt := weak.Make(bt)
193+
bt = nil
194+
time.Sleep(1 * time.Millisecond)
195+
bt = wt.Strong()
196+
if bt != nil {
197+
time.Sleep(4 * time.Millisecond)
198+
bt.t = bt
199+
bt.a = 12
200+
}
201+
runtime.KeepAlive(bt)
202+
}
203+
select {
204+
case <-ctx.Done():
205+
return
206+
default:
207+
}
208+
}
209+
}()
210+
}
211+
wg.Wait()
212+
}

src/runtime/mheap.go

+26-1
Original file line numberDiff line numberDiff line change
@@ -2073,14 +2073,32 @@ func internal_weak_runtime_makeStrongFromWeak(u unsafe.Pointer) unsafe.Pointer {
20732073
// Even if we just swept some random span that doesn't contain this object, because
20742074
// this object is long dead and its memory has since been reused, we'll just observe nil.
20752075
ptr := unsafe.Pointer(handle.Load())
2076+
2077+
// This is responsible for maintaining the same GC-related
2078+
// invariants as the Yuasa part of the write barrier. During
2079+
// the mark phase, it's possible that we just created the only
2080+
// valid pointer to the object pointed to by ptr. If it's only
2081+
// ever referenced from our stack, and our stack is blackened
2082+
// already, we could fail to mark it. So, mark it now.
2083+
if gcphase != _GCoff {
2084+
shade(uintptr(ptr))
2085+
}
20762086
releasem(mp)
2087+
2088+
// Explicitly keep ptr alive. This seems unnecessary since we return ptr,
2089+
// but let's be explicit since it's important we keep ptr alive across the
2090+
// call to shade.
2091+
KeepAlive(ptr)
20772092
return ptr
20782093
}
20792094

20802095
// Retrieves or creates a weak pointer handle for the object p.
20812096
func getOrAddWeakHandle(p unsafe.Pointer) *atomic.Uintptr {
20822097
// First try to retrieve without allocating.
20832098
if handle := getWeakHandle(p); handle != nil {
2099+
// Keep p alive for the duration of the function to ensure
2100+
// that it cannot die while we're trying to do this.
2101+
KeepAlive(p)
20842102
return handle
20852103
}
20862104

@@ -2105,6 +2123,10 @@ func getOrAddWeakHandle(p unsafe.Pointer) *atomic.Uintptr {
21052123
scanblock(uintptr(unsafe.Pointer(&s.handle)), goarch.PtrSize, &oneptrmask[0], gcw, nil)
21062124
releasem(mp)
21072125
}
2126+
2127+
// Keep p alive for the duration of the function to ensure
2128+
// that it cannot die while we're trying to do this.
2129+
KeepAlive(p)
21082130
return s.handle
21092131
}
21102132

@@ -2124,7 +2146,7 @@ func getOrAddWeakHandle(p unsafe.Pointer) *atomic.Uintptr {
21242146
}
21252147

21262148
// Keep p alive for the duration of the function to ensure
2127-
// that it cannot die while we're trying to this.
2149+
// that it cannot die while we're trying to do this.
21282150
KeepAlive(p)
21292151
return handle
21302152
}
@@ -2154,6 +2176,9 @@ func getWeakHandle(p unsafe.Pointer) *atomic.Uintptr {
21542176
unlock(&span.speciallock)
21552177
releasem(mp)
21562178

2179+
// Keep p alive for the duration of the function to ensure
2180+
// that it cannot die while we're trying to do this.
2181+
KeepAlive(p)
21572182
return handle
21582183
}
21592184

0 commit comments

Comments
 (0)