Source file src/weak/pointer_test.go

     1  // Copyright 2024 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package weak_test
     6  
     7  import (
     8  	"context"
     9  	"internal/goarch"
    10  	"runtime"
    11  	"sync"
    12  	"testing"
    13  	"time"
    14  	"unsafe"
    15  	"weak"
    16  )
    17  
    18  type T struct {
    19  	// N.B. This must contain a pointer, otherwise the weak handle might get placed
    20  	// in a tiny block making the tests in this package flaky.
    21  	t *T
    22  	a int
    23  	b int
    24  }
    25  
    26  func TestPointer(t *testing.T) {
    27  	var zero weak.Pointer[T]
    28  	if zero.Value() != nil {
    29  		t.Error("Value of zero value of weak.Pointer is not nil")
    30  	}
    31  	zeroNil := weak.Make[T](nil)
    32  	if zeroNil.Value() != nil {
    33  		t.Error("Value of weak.Make[T](nil) is not nil")
    34  	}
    35  
    36  	bt := new(T)
    37  	wt := weak.Make(bt)
    38  	if st := wt.Value(); st != bt {
    39  		t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt)
    40  	}
    41  	// bt is still referenced.
    42  	runtime.GC()
    43  
    44  	if st := wt.Value(); st != bt {
    45  		t.Fatalf("weak pointer is not the same as strong pointer after GC: %p vs. %p", st, bt)
    46  	}
    47  	// bt is no longer referenced.
    48  	runtime.GC()
    49  
    50  	if st := wt.Value(); st != nil {
    51  		t.Fatalf("expected weak pointer to be nil, got %p", st)
    52  	}
    53  }
    54  
    55  func TestPointerEquality(t *testing.T) {
    56  	var zero weak.Pointer[T]
    57  	zeroNil := weak.Make[T](nil)
    58  	if zero != zeroNil {
    59  		t.Error("weak.Make[T](nil) != zero value of weak.Pointer[T]")
    60  	}
    61  
    62  	bt := make([]*T, 10)
    63  	wt := make([]weak.Pointer[T], 10)
    64  	wo := make([]weak.Pointer[int], 10)
    65  	for i := range bt {
    66  		bt[i] = new(T)
    67  		wt[i] = weak.Make(bt[i])
    68  		wo[i] = weak.Make(&bt[i].a)
    69  	}
    70  	for i := range bt {
    71  		st := wt[i].Value()
    72  		if st != bt[i] {
    73  			t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt[i])
    74  		}
    75  		if wp := weak.Make(st); wp != wt[i] {
    76  			t.Fatalf("new weak pointer not equal to existing weak pointer: %v vs. %v", wp, wt[i])
    77  		}
    78  		if wp := weak.Make(&st.a); wp != wo[i] {
    79  			t.Fatalf("new weak pointer not equal to existing weak pointer: %v vs. %v", wp, wo[i])
    80  		}
    81  		if i == 0 {
    82  			continue
    83  		}
    84  		if wt[i] == wt[i-1] {
    85  			t.Fatalf("expected weak pointers to not be equal to each other, but got %v", wt[i])
    86  		}
    87  	}
    88  	// bt is still referenced.
    89  	runtime.GC()
    90  	for i := range bt {
    91  		st := wt[i].Value()
    92  		if st != bt[i] {
    93  			t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt[i])
    94  		}
    95  		if wp := weak.Make(st); wp != wt[i] {
    96  			t.Fatalf("new weak pointer not equal to existing weak pointer: %v vs. %v", wp, wt[i])
    97  		}
    98  		if wp := weak.Make(&st.a); wp != wo[i] {
    99  			t.Fatalf("new weak pointer not equal to existing weak pointer: %v vs. %v", wp, wo[i])
   100  		}
   101  		if i == 0 {
   102  			continue
   103  		}
   104  		if wt[i] == wt[i-1] {
   105  			t.Fatalf("expected weak pointers to not be equal to each other, but got %v", wt[i])
   106  		}
   107  	}
   108  	bt = nil
   109  	// bt is no longer referenced.
   110  	runtime.GC()
   111  	for i := range bt {
   112  		st := wt[i].Value()
   113  		if st != nil {
   114  			t.Fatalf("expected weak pointer to be nil, got %p", st)
   115  		}
   116  		if i == 0 {
   117  			continue
   118  		}
   119  		if wt[i] == wt[i-1] {
   120  			t.Fatalf("expected weak pointers to not be equal to each other, but got %v", wt[i])
   121  		}
   122  	}
   123  }
   124  
   125  func TestPointerFinalizer(t *testing.T) {
   126  	bt := new(T)
   127  	wt := weak.Make(bt)
   128  	done := make(chan struct{}, 1)
   129  	runtime.SetFinalizer(bt, func(bt *T) {
   130  		if wt.Value() != nil {
   131  			t.Errorf("weak pointer did not go nil before finalizer ran")
   132  		}
   133  		done <- struct{}{}
   134  	})
   135  
   136  	// Make sure the weak pointer stays around while bt is live.
   137  	runtime.GC()
   138  	if wt.Value() == nil {
   139  		t.Errorf("weak pointer went nil too soon")
   140  	}
   141  	runtime.KeepAlive(bt)
   142  
   143  	// bt is no longer referenced.
   144  	//
   145  	// Run one cycle to queue the finalizer.
   146  	runtime.GC()
   147  	if wt.Value() != nil {
   148  		t.Errorf("weak pointer did not go nil when finalizer was enqueued")
   149  	}
   150  
   151  	// Wait for the finalizer to run.
   152  	<-done
   153  
   154  	// The weak pointer should still be nil after the finalizer runs.
   155  	runtime.GC()
   156  	if wt.Value() != nil {
   157  		t.Errorf("weak pointer is non-nil even after finalization: %v", wt)
   158  	}
   159  }
   160  
   161  func TestPointerCleanup(t *testing.T) {
   162  	bt := new(T)
   163  	wt := weak.Make(bt)
   164  	done := make(chan struct{}, 1)
   165  	runtime.AddCleanup(bt, func(_ bool) {
   166  		if wt.Value() != nil {
   167  			t.Errorf("weak pointer did not go nil before cleanup was executed")
   168  		}
   169  		done <- struct{}{}
   170  	}, true)
   171  
   172  	// Make sure the weak pointer stays around while bt is live.
   173  	runtime.GC()
   174  	if wt.Value() == nil {
   175  		t.Errorf("weak pointer went nil too soon")
   176  	}
   177  	runtime.KeepAlive(bt)
   178  
   179  	// bt is no longer referenced.
   180  	//
   181  	// Run one cycle to queue the cleanup.
   182  	runtime.GC()
   183  	if wt.Value() != nil {
   184  		t.Errorf("weak pointer did not go nil when cleanup was enqueued")
   185  	}
   186  
   187  	// Wait for the cleanup to run.
   188  	<-done
   189  
   190  	// The weak pointer should still be nil after the cleanup runs.
   191  	runtime.GC()
   192  	if wt.Value() != nil {
   193  		t.Errorf("weak pointer is non-nil even after cleanup: %v", wt)
   194  	}
   195  }
   196  
   197  func TestPointerSize(t *testing.T) {
   198  	var p weak.Pointer[T]
   199  	size := unsafe.Sizeof(p)
   200  	if size != goarch.PtrSize {
   201  		t.Errorf("weak.Pointer[T] size = %d, want %d", size, goarch.PtrSize)
   202  	}
   203  }
   204  
   205  // Regression test for issue 69210.
   206  //
   207  // Weak-to-strong conversions must shade the new strong pointer, otherwise
   208  // that might be creating the only strong pointer to a white object which
   209  // is hidden in a blackened stack.
   210  //
   211  // Never fails if correct, fails with some high probability if incorrect.
   212  func TestIssue69210(t *testing.T) {
   213  	if testing.Short() {
   214  		t.Skip("this is a stress test that takes seconds to run on its own")
   215  	}
   216  	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
   217  	defer cancel()
   218  
   219  	// What we're trying to do is manufacture the conditions under which this
   220  	// bug happens. Specifically, we want:
   221  	//
   222  	// 1. To create a whole bunch of objects that are only weakly-pointed-to,
   223  	// 2. To call Value while the GC is in the mark phase,
   224  	// 3. The new strong pointer to be missed by the GC,
   225  	// 4. The following GC cycle to mark a free object.
   226  	//
   227  	// Unfortunately, (2) and (3) are hard to control, but we can increase
   228  	// the likelihood by having several goroutines do (1) at once while
   229  	// another goroutine constantly keeps us in the GC with runtime.GC.
   230  	// Like throwing darts at a dart board until they land just right.
   231  	// We can increase the likelihood of (4) by adding some delay after
   232  	// creating the strong pointer, but only if it's non-nil. If it's nil,
   233  	// that means it was already collected in which case there's no chance
   234  	// of triggering the bug, so we want to retry as fast as possible.
   235  	// Our heap here is tiny, so the GCs will go by fast.
   236  	//
   237  	// As of 2024-09-03, removing the line that shades pointers during
   238  	// the weak-to-strong conversion causes this test to fail about 50%
   239  	// of the time.
   240  
   241  	var wg sync.WaitGroup
   242  	wg.Add(1)
   243  	go func() {
   244  		defer wg.Done()
   245  		for {
   246  			runtime.GC()
   247  
   248  			select {
   249  			case <-ctx.Done():
   250  				return
   251  			default:
   252  			}
   253  		}
   254  	}()
   255  	for range max(runtime.GOMAXPROCS(-1)-1, 1) {
   256  		wg.Add(1)
   257  		go func() {
   258  			defer wg.Done()
   259  			for {
   260  				for range 5 {
   261  					bt := new(T)
   262  					wt := weak.Make(bt)
   263  					bt = nil
   264  					time.Sleep(1 * time.Millisecond)
   265  					bt = wt.Value()
   266  					if bt != nil {
   267  						time.Sleep(4 * time.Millisecond)
   268  						bt.t = bt
   269  						bt.a = 12
   270  					}
   271  					runtime.KeepAlive(bt)
   272  				}
   273  				select {
   274  				case <-ctx.Done():
   275  					return
   276  				default:
   277  				}
   278  			}
   279  		}()
   280  	}
   281  	wg.Wait()
   282  }
   283  
   284  func TestIssue70739(t *testing.T) {
   285  	x := make([]*int, 4<<16)
   286  	wx1 := weak.Make(&x[1<<16])
   287  	wx2 := weak.Make(&x[1<<16])
   288  	if wx1 != wx2 {
   289  		t.Fatal("failed to look up special and made duplicate weak handle; see issue #70739")
   290  	}
   291  }
   292  
   293  var immortal T
   294  
   295  func TestImmortalPointer(t *testing.T) {
   296  	w0 := weak.Make(&immortal)
   297  	if weak.Make(&immortal) != w0 {
   298  		t.Error("immortal weak pointers to the same pointer not equal")
   299  	}
   300  	w0a := weak.Make(&immortal.a)
   301  	w0b := weak.Make(&immortal.b)
   302  	if w0a == w0b {
   303  		t.Error("separate immortal pointers (same object) have the same pointer")
   304  	}
   305  	if got, want := w0.Value(), &immortal; got != want {
   306  		t.Errorf("immortal weak pointer to %p has unexpected Value %p", want, got)
   307  	}
   308  	if got, want := w0a.Value(), &immortal.a; got != want {
   309  		t.Errorf("immortal weak pointer to %p has unexpected Value %p", want, got)
   310  	}
   311  	if got, want := w0b.Value(), &immortal.b; got != want {
   312  		t.Errorf("immortal weak pointer to %p has unexpected Value %p", want, got)
   313  	}
   314  
   315  	// Run a couple of cycles.
   316  	runtime.GC()
   317  	runtime.GC()
   318  
   319  	// All immortal weak pointers should never get cleared.
   320  	if got, want := w0.Value(), &immortal; got != want {
   321  		t.Errorf("immortal weak pointer to %p has unexpected Value %p", want, got)
   322  	}
   323  	if got, want := w0a.Value(), &immortal.a; got != want {
   324  		t.Errorf("immortal weak pointer to %p has unexpected Value %p", want, got)
   325  	}
   326  	if got, want := w0b.Value(), &immortal.b; got != want {
   327  		t.Errorf("immortal weak pointer to %p has unexpected Value %p", want, got)
   328  	}
   329  }
   330  

View as plain text