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  	bt := new(T)
    25  	wt := weak.Make(bt)
    26  	if st := wt.Value(); st != bt {
    27  		t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt)
    28  	}
    29  	// bt is still referenced.
    30  	runtime.GC()
    31  
    32  	if st := wt.Value(); st != bt {
    33  		t.Fatalf("weak pointer is not the same as strong pointer after GC: %p vs. %p", st, bt)
    34  	}
    35  	// bt is no longer referenced.
    36  	runtime.GC()
    37  
    38  	if st := wt.Value(); st != nil {
    39  		t.Fatalf("expected weak pointer to be nil, got %p", st)
    40  	}
    41  }
    42  
    43  func TestPointerEquality(t *testing.T) {
    44  	bt := make([]*T, 10)
    45  	wt := make([]weak.Pointer[T], 10)
    46  	for i := range bt {
    47  		bt[i] = new(T)
    48  		wt[i] = weak.Make(bt[i])
    49  	}
    50  	for i := range bt {
    51  		st := wt[i].Value()
    52  		if st != bt[i] {
    53  			t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt[i])
    54  		}
    55  		if wp := weak.Make(st); wp != wt[i] {
    56  			t.Fatalf("new weak pointer not equal to existing weak pointer: %v vs. %v", wp, wt[i])
    57  		}
    58  		if i == 0 {
    59  			continue
    60  		}
    61  		if wt[i] == wt[i-1] {
    62  			t.Fatalf("expected weak pointers to not be equal to each other, but got %v", wt[i])
    63  		}
    64  	}
    65  	// bt is still referenced.
    66  	runtime.GC()
    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 i == 0 {
    76  			continue
    77  		}
    78  		if wt[i] == wt[i-1] {
    79  			t.Fatalf("expected weak pointers to not be equal to each other, but got %v", wt[i])
    80  		}
    81  	}
    82  	bt = nil
    83  	// bt is no longer referenced.
    84  	runtime.GC()
    85  	for i := range bt {
    86  		st := wt[i].Value()
    87  		if st != nil {
    88  			t.Fatalf("expected weak pointer to be nil, got %p", st)
    89  		}
    90  		if i == 0 {
    91  			continue
    92  		}
    93  		if wt[i] == wt[i-1] {
    94  			t.Fatalf("expected weak pointers to not be equal to each other, but got %v", wt[i])
    95  		}
    96  	}
    97  }
    98  
    99  func TestPointerFinalizer(t *testing.T) {
   100  	bt := new(T)
   101  	wt := weak.Make(bt)
   102  	done := make(chan struct{}, 1)
   103  	runtime.SetFinalizer(bt, func(bt *T) {
   104  		if wt.Value() != nil {
   105  			t.Errorf("weak pointer did not go nil before finalizer ran")
   106  		}
   107  		done <- struct{}{}
   108  	})
   109  
   110  	// Make sure the weak pointer stays around while bt is live.
   111  	runtime.GC()
   112  	if wt.Value() == nil {
   113  		t.Errorf("weak pointer went nil too soon")
   114  	}
   115  	runtime.KeepAlive(bt)
   116  
   117  	// bt is no longer referenced.
   118  	//
   119  	// Run one cycle to queue the finalizer.
   120  	runtime.GC()
   121  	if wt.Value() != nil {
   122  		t.Errorf("weak pointer did not go nil when finalizer was enqueued")
   123  	}
   124  
   125  	// Wait for the finalizer to run.
   126  	<-done
   127  
   128  	// The weak pointer should still be nil after the finalizer runs.
   129  	runtime.GC()
   130  	if wt.Value() != nil {
   131  		t.Errorf("weak pointer is non-nil even after finalization: %v", wt)
   132  	}
   133  }
   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 Value 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.Value()
   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  }
   213  

View as plain text