Source file src/runtime/synctest.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 runtime
     6  
     7  import (
     8  	"unsafe"
     9  )
    10  
    11  // A synctestGroup is a group of goroutines started by synctest.Run.
    12  type synctestGroup struct {
    13  	mu      mutex
    14  	timers  timers
    15  	now     int64 // current fake time
    16  	root    *g    // caller of synctest.Run
    17  	waiter  *g    // caller of synctest.Wait
    18  	waiting bool  // true if a goroutine is calling synctest.Wait
    19  
    20  	// The group is active (not blocked) so long as running > 0 || active > 0.
    21  	//
    22  	// running is the number of goroutines which are not "durably blocked":
    23  	// Goroutines which are either running, runnable, or non-durably blocked
    24  	// (for example, blocked in a syscall).
    25  	//
    26  	// active is used to keep the group from becoming blocked,
    27  	// even if all goroutines in the group are blocked.
    28  	// For example, park_m can choose to immediately unpark a goroutine after parking it.
    29  	// It increments the active count to keep the group active until it has determined
    30  	// that the park operation has completed.
    31  	total   int // total goroutines
    32  	running int // non-blocked goroutines
    33  	active  int // other sources of activity
    34  }
    35  
    36  // changegstatus is called when the non-lock status of a g changes.
    37  // It is never called with a Gscanstatus.
    38  func (sg *synctestGroup) changegstatus(gp *g, oldval, newval uint32) {
    39  	// Determine whether this change in status affects the idleness of the group.
    40  	// If this isn't a goroutine starting, stopping, durably blocking,
    41  	// or waking up after durably blocking, then return immediately without
    42  	// locking sg.mu.
    43  	//
    44  	// For example, stack growth (newstack) will changegstatus
    45  	// from _Grunning to _Gcopystack. This is uninteresting to synctest,
    46  	// but if stack growth occurs while sg.mu is held, we must not recursively lock.
    47  	totalDelta := 0
    48  	wasRunning := true
    49  	switch oldval {
    50  	case _Gdead:
    51  		wasRunning = false
    52  		totalDelta++
    53  	case _Gwaiting:
    54  		if gp.waitreason.isIdleInSynctest() {
    55  			wasRunning = false
    56  		}
    57  	}
    58  	isRunning := true
    59  	switch newval {
    60  	case _Gdead:
    61  		isRunning = false
    62  		totalDelta--
    63  	case _Gwaiting:
    64  		if gp.waitreason.isIdleInSynctest() {
    65  			isRunning = false
    66  		}
    67  	}
    68  	// It's possible for wasRunning == isRunning while totalDelta != 0;
    69  	// for example, if a new goroutine is created in a non-running state.
    70  	if wasRunning == isRunning && totalDelta == 0 {
    71  		return
    72  	}
    73  
    74  	lock(&sg.mu)
    75  	sg.total += totalDelta
    76  	if wasRunning != isRunning {
    77  		if isRunning {
    78  			sg.running++
    79  		} else {
    80  			sg.running--
    81  			if raceenabled && newval != _Gdead {
    82  				racereleasemergeg(gp, sg.raceaddr())
    83  			}
    84  		}
    85  	}
    86  	if sg.total < 0 {
    87  		fatal("total < 0")
    88  	}
    89  	if sg.running < 0 {
    90  		fatal("running < 0")
    91  	}
    92  	wake := sg.maybeWakeLocked()
    93  	unlock(&sg.mu)
    94  	if wake != nil {
    95  		goready(wake, 0)
    96  	}
    97  }
    98  
    99  // incActive increments the active-count for the group.
   100  // A group does not become durably blocked while the active-count is non-zero.
   101  func (sg *synctestGroup) incActive() {
   102  	lock(&sg.mu)
   103  	sg.active++
   104  	unlock(&sg.mu)
   105  }
   106  
   107  // decActive decrements the active-count for the group.
   108  func (sg *synctestGroup) decActive() {
   109  	lock(&sg.mu)
   110  	sg.active--
   111  	if sg.active < 0 {
   112  		throw("active < 0")
   113  	}
   114  	wake := sg.maybeWakeLocked()
   115  	unlock(&sg.mu)
   116  	if wake != nil {
   117  		goready(wake, 0)
   118  	}
   119  }
   120  
   121  // maybeWakeLocked returns a g to wake if the group is durably blocked.
   122  func (sg *synctestGroup) maybeWakeLocked() *g {
   123  	if sg.running > 0 || sg.active > 0 {
   124  		return nil
   125  	}
   126  	// Increment the group active count, since we've determined to wake something.
   127  	// The woken goroutine will decrement the count.
   128  	// We can't just call goready and let it increment sg.running,
   129  	// since we can't call goready with sg.mu held.
   130  	//
   131  	// Incrementing the active count here is only necessary if something has gone wrong,
   132  	// and a goroutine that we considered durably blocked wakes up unexpectedly.
   133  	// Two wakes happening at the same time leads to very confusing failure modes,
   134  	// so we take steps to avoid it happening.
   135  	sg.active++
   136  	if gp := sg.waiter; gp != nil {
   137  		// A goroutine is blocked in Wait. Wake it.
   138  		return gp
   139  	}
   140  	// All goroutines in the group are durably blocked, and nothing has called Wait.
   141  	// Wake the root goroutine.
   142  	return sg.root
   143  }
   144  
   145  func (sg *synctestGroup) raceaddr() unsafe.Pointer {
   146  	// Address used to record happens-before relationships created by the group.
   147  	//
   148  	// Wait creates a happens-before relationship between itself and
   149  	// the blocking operations which caused other goroutines in the group to park.
   150  	return unsafe.Pointer(sg)
   151  }
   152  
   153  //go:linkname synctestRun internal/synctest.Run
   154  func synctestRun(f func()) {
   155  	if debug.asynctimerchan.Load() != 0 {
   156  		panic("synctest.Run not supported with asynctimerchan!=0")
   157  	}
   158  
   159  	gp := getg()
   160  	if gp.syncGroup != nil {
   161  		panic("synctest.Run called from within a synctest bubble")
   162  	}
   163  	gp.syncGroup = &synctestGroup{
   164  		total:   1,
   165  		running: 1,
   166  		root:    gp,
   167  	}
   168  	const synctestBaseTime = 946684800000000000 // midnight UTC 2000-01-01
   169  	gp.syncGroup.now = synctestBaseTime
   170  	gp.syncGroup.timers.syncGroup = gp.syncGroup
   171  	lockInit(&gp.syncGroup.mu, lockRankSynctest)
   172  	lockInit(&gp.syncGroup.timers.mu, lockRankTimers)
   173  	defer func() {
   174  		gp.syncGroup = nil
   175  	}()
   176  
   177  	fv := *(**funcval)(unsafe.Pointer(&f))
   178  	newproc(fv)
   179  
   180  	sg := gp.syncGroup
   181  	lock(&sg.mu)
   182  	sg.active++
   183  	for {
   184  		if raceenabled {
   185  			raceacquireg(gp, gp.syncGroup.raceaddr())
   186  		}
   187  		unlock(&sg.mu)
   188  		systemstack(func() {
   189  			gp.syncGroup.timers.check(gp.syncGroup.now)
   190  		})
   191  		gopark(synctestidle_c, nil, waitReasonSynctestRun, traceBlockSynctest, 0)
   192  		lock(&sg.mu)
   193  		if sg.active < 0 {
   194  			throw("active < 0")
   195  		}
   196  		next := sg.timers.wakeTime()
   197  		if next == 0 {
   198  			break
   199  		}
   200  		if next < sg.now {
   201  			throw("time went backwards")
   202  		}
   203  		sg.now = next
   204  	}
   205  
   206  	total := sg.total
   207  	unlock(&sg.mu)
   208  	if total != 1 {
   209  		panic("deadlock: all goroutines in bubble are blocked")
   210  	}
   211  	if gp.timer != nil && gp.timer.isFake {
   212  		// Verify that we haven't marked this goroutine's sleep timer as fake.
   213  		// This could happen if something in Run were to call timeSleep.
   214  		throw("synctest root goroutine has a fake timer")
   215  	}
   216  }
   217  
   218  func synctestidle_c(gp *g, _ unsafe.Pointer) bool {
   219  	lock(&gp.syncGroup.mu)
   220  	canIdle := true
   221  	if gp.syncGroup.running == 0 && gp.syncGroup.active == 1 {
   222  		// All goroutines in the group have blocked or exited.
   223  		canIdle = false
   224  	} else {
   225  		gp.syncGroup.active--
   226  	}
   227  	unlock(&gp.syncGroup.mu)
   228  	return canIdle
   229  }
   230  
   231  //go:linkname synctestWait internal/synctest.Wait
   232  func synctestWait() {
   233  	gp := getg()
   234  	if gp.syncGroup == nil {
   235  		panic("goroutine is not in a bubble")
   236  	}
   237  	lock(&gp.syncGroup.mu)
   238  	// We use a syncGroup.waiting bool to detect simultaneous calls to Wait rather than
   239  	// checking to see if syncGroup.waiter is non-nil. This avoids a race between unlocking
   240  	// syncGroup.mu and setting syncGroup.waiter while parking.
   241  	if gp.syncGroup.waiting {
   242  		unlock(&gp.syncGroup.mu)
   243  		panic("wait already in progress")
   244  	}
   245  	gp.syncGroup.waiting = true
   246  	unlock(&gp.syncGroup.mu)
   247  	gopark(synctestwait_c, nil, waitReasonSynctestWait, traceBlockSynctest, 0)
   248  
   249  	lock(&gp.syncGroup.mu)
   250  	gp.syncGroup.active--
   251  	if gp.syncGroup.active < 0 {
   252  		throw("active < 0")
   253  	}
   254  	gp.syncGroup.waiter = nil
   255  	gp.syncGroup.waiting = false
   256  	unlock(&gp.syncGroup.mu)
   257  
   258  	// Establish a happens-before relationship on the activity of the now-blocked
   259  	// goroutines in the group.
   260  	if raceenabled {
   261  		raceacquireg(gp, gp.syncGroup.raceaddr())
   262  	}
   263  }
   264  
   265  func synctestwait_c(gp *g, _ unsafe.Pointer) bool {
   266  	lock(&gp.syncGroup.mu)
   267  	if gp.syncGroup.running == 0 && gp.syncGroup.active == 0 {
   268  		// This shouldn't be possible, since gopark increments active during unlockf.
   269  		throw("running == 0 && active == 0")
   270  	}
   271  	gp.syncGroup.waiter = gp
   272  	unlock(&gp.syncGroup.mu)
   273  	return true
   274  }
   275  
   276  //go:linkname synctest_acquire internal/synctest.acquire
   277  func synctest_acquire() any {
   278  	if sg := getg().syncGroup; sg != nil {
   279  		sg.incActive()
   280  		return sg
   281  	}
   282  	return nil
   283  }
   284  
   285  //go:linkname synctest_release internal/synctest.release
   286  func synctest_release(sg any) {
   287  	sg.(*synctestGroup).decActive()
   288  }
   289  
   290  //go:linkname synctest_inBubble internal/synctest.inBubble
   291  func synctest_inBubble(sg any, f func()) {
   292  	gp := getg()
   293  	if gp.syncGroup != nil {
   294  		panic("goroutine is already bubbled")
   295  	}
   296  	gp.syncGroup = sg.(*synctestGroup)
   297  	defer func() {
   298  		gp.syncGroup = nil
   299  	}()
   300  	f()
   301  }
   302  

View as plain text