|  | @@ -226,3 +226,100 @@ func TestWaitGroup(t *testing.T) {
 | 
	
		
			
				|  |  |  	debug = false
 | 
	
		
			
				|  |  |  	l.SetDebug("sync", false)
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +func TestTimeoutCond(t *testing.T) {
 | 
	
		
			
				|  |  | +	// WARNING this test relies heavily on threads not being stalled at particular points.
 | 
	
		
			
				|  |  | +	// As such, it's pretty unstable on the build server. It has been left in as it still
 | 
	
		
			
				|  |  | +	// exercises the deadlock detector, and one of the two things it tests is still functional.
 | 
	
		
			
				|  |  | +	// See the comments in runLocks
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	const (
 | 
	
		
			
				|  |  | +		// Low values to avoid being intrusive in continous testing. Can be
 | 
	
		
			
				|  |  | +		// increased significantly for stress testing.
 | 
	
		
			
				|  |  | +		iterations = 100
 | 
	
		
			
				|  |  | +		routines   = 10
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		timeMult = 2
 | 
	
		
			
				|  |  | +	)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	c := NewTimeoutCond(NewMutex())
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	// Start a routine to periodically broadcast on the cond.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	go func() {
 | 
	
		
			
				|  |  | +		d := time.Duration(routines) * timeMult * time.Millisecond / 2
 | 
	
		
			
				|  |  | +		t.Log("Broadcasting every", d)
 | 
	
		
			
				|  |  | +		for i := 0; i < iterations; i++ {
 | 
	
		
			
				|  |  | +			time.Sleep(d)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			c.L.Lock()
 | 
	
		
			
				|  |  | +			c.Broadcast()
 | 
	
		
			
				|  |  | +			c.L.Unlock()
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +	}()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	// Start several routines that wait on it with different timeouts.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	var results [routines][2]int
 | 
	
		
			
				|  |  | +	var wg sync.WaitGroup
 | 
	
		
			
				|  |  | +	for i := 0; i < routines; i++ {
 | 
	
		
			
				|  |  | +		i := i
 | 
	
		
			
				|  |  | +		wg.Add(1)
 | 
	
		
			
				|  |  | +		go func() {
 | 
	
		
			
				|  |  | +			d := time.Duration(i) * timeMult * time.Millisecond
 | 
	
		
			
				|  |  | +			t.Logf("Routine %d waits for %v\n", i, d)
 | 
	
		
			
				|  |  | +			succ, fail := runLocks(t, iterations, c, d)
 | 
	
		
			
				|  |  | +			results[i][0] = succ
 | 
	
		
			
				|  |  | +			results[i][1] = fail
 | 
	
		
			
				|  |  | +			wg.Done()
 | 
	
		
			
				|  |  | +		}()
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	wg.Wait()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	// Print a table of routine number: successes, failures.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	for i, v := range results {
 | 
	
		
			
				|  |  | +		t.Logf("%4d: %4d %4d\n", i, v[0], v[1])
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +func runLocks(t *testing.T, iterations int, c *TimeoutCond, d time.Duration) (succ, fail int) {
 | 
	
		
			
				|  |  | +	for i := 0; i < iterations; i++ {
 | 
	
		
			
				|  |  | +		c.L.Lock()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		// The thread may be stalled, so we can't test the 'succeeded late' case reliably.
 | 
	
		
			
				|  |  | +		// Therefore make sure that we start t0 before starting the timeout, and only test
 | 
	
		
			
				|  |  | +		// the 'failed early' case.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		t0 := time.Now()
 | 
	
		
			
				|  |  | +		w := c.SetupWait(d)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		res := w.Wait()
 | 
	
		
			
				|  |  | +		waited := time.Since(t0)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		// Allow 20% slide in either direction, and a five milliseconds of
 | 
	
		
			
				|  |  | +		// scheduling delay... In tweaking these it was clear that things
 | 
	
		
			
				|  |  | +		// worked like the should, so if this becomes a spurious failure
 | 
	
		
			
				|  |  | +		// kind of thing feel free to remove or give significantly more
 | 
	
		
			
				|  |  | +		// slack.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		if !res && waited < d*8/10 {
 | 
	
		
			
				|  |  | +			t.Errorf("Wait failed early, %v < %v", waited, d)
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +		if res && waited > d*11/10+5*time.Millisecond {
 | 
	
		
			
				|  |  | +			// Ideally this would be t.Errorf
 | 
	
		
			
				|  |  | +			t.Logf("WARNING: Wait succeeded late, %v > %v. This is probably a thread scheduling issue", waited, d)
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		w.Stop()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		if res {
 | 
	
		
			
				|  |  | +			succ++
 | 
	
		
			
				|  |  | +		} else {
 | 
	
		
			
				|  |  | +			fail++
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +		c.L.Unlock()
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +	return
 | 
	
		
			
				|  |  | +}
 |