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  	"runtime"
    10  	"sync"
    11  	"testing"
    12  	"time"
    13  	"weak"
    14  )
    15  
    16  type T struct {
    17  	// N.B. This must contain a pointer, otherwise the weak handle might get placed
    18  	// in a tiny block making the tests in this package flaky.
    19  	t *T
    20  	a int
    21  }
    22  
    23  func TestPointer(t *testing.T) {
    24  	var zero weak.Pointer[T]
    25  	if zero.Value() != nil {
    26  		t.Error("Value of zero value of weak.Pointer is not nil")
    27  	}
    28  	zeroNil := weak.Make[T](nil)
    29  	if zeroNil.Value() != nil {
    30  		t.Error("Value of weak.Make[T](nil) is not nil")
    31  	}
    32  
    33  	bt := new(T)
    34  	wt := weak.Make(bt)
    35  	if st := wt.Value(); st != bt {
    36  		t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt)
    37  	}
    38  	// bt is still referenced.
    39  	runtime.GC()
    40  
    41  	if st := wt.Value(); st != bt {
    42  		t.Fatalf("weak pointer is not the same as strong pointer after GC: %p vs. %p", st, bt)
    43  	}
    44  	// bt is no longer referenced.
    45  	runtime.GC()
    46  
    47  	if st := wt.Value(); st != nil {
    48  		t.Fatalf("expected weak pointer to be nil, got %p", st)
    49  	}
    50  }
    51  
    52  func TestPointerEquality(t *testing.T) {
    53  	var zero weak.Pointer[T]
    54  	zeroNil := weak.Make[T](nil)
    55  	if zero != zeroNil {
    56  		t.Error("weak.Make[T](nil) != zero value of weak.Pointer[T]")
    57  	}
    58  
    59  	bt := make([]*T, 10)
    60  	wt := make([]weak.Pointer[T], 10)
    61  	wo := make([]weak.Pointer[int], 10)
    62  	for i := range bt {
    63  		bt[i] = new(T)
    64  		wt[i] = weak.Make(bt[i])
    65  		wo[i] = weak.Make(&bt[i].a)
    66  	}
    67  	for i := range bt {
    68  		st := wt[i].Value()
    69  		if st != bt[i] {
    70  			t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt[i])
    71  		}
    72  		if wp := weak.Make(st); wp != wt[i] {
    73  			t.Fatalf("new weak pointer not equal to existing weak pointer: %v vs. %v", wp, wt[i])
    74  		}
    75  		if wp := weak.Make(&st.a); wp != wo[i] {
    76  			t.Fatalf("new weak pointer not equal to existing weak pointer: %v vs. %v", wp, wo[i])
    77  		}
    78  		if i == 0 {
    79  			continue
    80  		}
    81  		if wt[i] == wt[i-1] {
    82  			t.Fatalf("expected weak pointers to not be equal to each other, but got %v", wt[i])
    83  		}
    84  	}
    85  	// bt is still referenced.
    86  	runtime.GC()
    87  	for i := range bt {
    88  		st := wt[i].Value()
    89  		if st != bt[i] {
    90  			t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt[i])
    91  		}
    92  		if wp := weak.Make(st); wp != wt[i] {
    93  			t.Fatalf("new weak pointer not equal to existing weak pointer: %v vs. %v", wp, wt[i])
    94  		}
    95  		if wp := weak.Make(&st.a); wp != wo[i] {
    96  			t.Fatalf("new weak pointer not equal to existing weak pointer: %v vs. %v", wp, wo[i])
    97  		}
    98  		if i == 0 {
    99  			continue
   100  		}
   101  		if wt[i] == wt[i-1] {
   102  			t.Fatalf("expected weak pointers to not be equal to each other, but got %v", wt[i])
   103  		}
   104  	}
   105  	bt = nil
   106  	// bt is no longer referenced.
   107  	runtime.GC()
   108  	for i := range bt {
   109  		st := wt[i].Value()
   110  		if st != nil {
   111  			t.Fatalf("expected weak pointer to be nil, got %p", st)
   112  		}
   113  		if i == 0 {
   114  			continue
   115  		}
   116  		if wt[i] == wt[i-1] {
   117  			t.Fatalf("expected weak pointers to not be equal to each other, but got %v", wt[i])
   118  		}
   119  	}
   120  }
   121  
   122  func TestPointerFinalizer(t *testing.T) {
   123  	bt := new(T)
   124  	wt := weak.Make(bt)
   125  	done := make(chan struct{}, 1)
   126  	runtime.SetFinalizer(bt, func(bt *T) {
   127  		if wt.Value() != nil {
   128  			t.Errorf("weak pointer did not go nil before finalizer ran")
   129  		}
   130  		done <- struct{}{}
   131  	})
   132  
   133  	// Make sure the weak pointer stays around while bt is live.
   134  	runtime.GC()
   135  	if wt.Value() == nil {
   136  		t.Errorf("weak pointer went nil too soon")
   137  	}
   138  	runtime.KeepAlive(bt)
   139  
   140  	// bt is no longer referenced.
   141  	//
   142  	// Run one cycle to queue the finalizer.
   143  	runtime.GC()
   144  	if wt.Value() != nil {
   145  		t.Errorf("weak pointer did not go nil when finalizer was enqueued")
   146  	}
   147  
   148  	// Wait for the finalizer to run.
   149  	<-done
   150  
   151  	// The weak pointer should still be nil after the finalizer runs.
   152  	runtime.GC()
   153  	if wt.Value() != nil {
   154  		t.Errorf("weak pointer is non-nil even after finalization: %v", wt)
   155  	}
   156  }
   157  
   158  // Regression test for issue 69210.
   159  //
   160  // Weak-to-strong conversions must shade the new strong pointer, otherwise
   161  // that might be creating the only strong pointer to a white object which
   162  // is hidden in a blackened stack.
   163  //
   164  // Never fails if correct, fails with some high probability if incorrect.
   165  func TestIssue69210(t *testing.T) {
   166  	if testing.Short() {
   167  		t.Skip("this is a stress test that takes seconds to run on its own")
   168  	}
   169  	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
   170  	defer cancel()
   171  
   172  	// What we're trying to do is manufacture the conditions under which this
   173  	// bug happens. Specifically, we want:
   174  	//
   175  	// 1. To create a whole bunch of objects that are only weakly-pointed-to,
   176  	// 2. To call Value while the GC is in the mark phase,
   177  	// 3. The new strong pointer to be missed by the GC,
   178  	// 4. The following GC cycle to mark a free object.
   179  	//
   180  	// Unfortunately, (2) and (3) are hard to control, but we can increase
   181  	// the likelihood by having several goroutines do (1) at once while
   182  	// another goroutine constantly keeps us in the GC with runtime.GC.
   183  	// Like throwing darts at a dart board until they land just right.
   184  	// We can increase the likelihood of (4) by adding some delay after
   185  	// creating the strong pointer, but only if it's non-nil. If it's nil,
   186  	// that means it was already collected in which case there's no chance
   187  	// of triggering the bug, so we want to retry as fast as possible.
   188  	// Our heap here is tiny, so the GCs will go by fast.
   189  	//
   190  	// As of 2024-09-03, removing the line that shades pointers during
   191  	// the weak-to-strong conversion causes this test to fail about 50%
   192  	// of the time.
   193  
   194  	var wg sync.WaitGroup
   195  	wg.Add(1)
   196  	go func() {
   197  		defer wg.Done()
   198  		for {
   199  			runtime.GC()
   200  
   201  			select {
   202  			case <-ctx.Done():
   203  				return
   204  			default:
   205  			}
   206  		}
   207  	}()
   208  	for range max(runtime.GOMAXPROCS(-1)-1, 1) {
   209  		wg.Add(1)
   210  		go func() {
   211  			defer wg.Done()
   212  			for {
   213  				for range 5 {
   214  					bt := new(T)
   215  					wt := weak.Make(bt)
   216  					bt = nil
   217  					time.Sleep(1 * time.Millisecond)
   218  					bt = wt.Value()
   219  					if bt != nil {
   220  						time.Sleep(4 * time.Millisecond)
   221  						bt.t = bt
   222  						bt.a = 12
   223  					}
   224  					runtime.KeepAlive(bt)
   225  				}
   226  				select {
   227  				case <-ctx.Done():
   228  					return
   229  				default:
   230  				}
   231  			}
   232  		}()
   233  	}
   234  	wg.Wait()
   235  }
   236  
   237  func TestIssue70739(t *testing.T) {
   238  	x := make([]*int, 4<<16)
   239  	wx1 := weak.Make(&x[1<<16])
   240  	wx2 := weak.Make(&x[1<<16])
   241  	if wx1 != wx2 {
   242  		t.Fatal("failed to look up special and made duplicate weak handle; see issue #70739")
   243  	}
   244  }
   245  

View as plain text