blob: 0cdc87162cfc1da0dd34ba21ae7e2906203009dd [file] [log] [blame]
// Copyright 2016 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This test runs too slowly under the race detector.
// +build !race
package nsync_test
import "fmt"
import "math/rand"
import "testing"
import "time"
import "v.io/x/lib/nsync"
// ---------------------------
// A cvStressData represents the data used by the threads of TestCVWaitStress.
type cvStressData struct {
mu nsync.Mu // protects fields below
count uint64 // incremented by the various threads
timeouts uint64 // incremented on each timeout
refs uint // reference count: one per test thread, decremented when it exits
countIsIMod4 [4]nsync.CV // element i signalled when count==i mod 4
refsIsZero nsync.CV // signalled when refs==0
}
// The delay in cvStressIncLoop() is uniformly distributed from 0 to
// cvMaxDelayMicros-1 microseconds.
const cvMaxDelayMicros = 1000 // maximum delay
const cvMeanDelayMicros = cvMaxDelayMicros / 2 // mean delay
const cvExpectedTimeoutsPerSec = 1000000 / cvMeanDelayMicros // number of timeouts expected per second
// cvStressIncLoop() acquires s.mu, then increments s.count n times, each time
// waiting until condition is true. A random delay between 0us and 999us is
// used for each wait; if the timeout expires, s.timeouts is incremented, and
// the wait is retried. s.refs is decremented before the routine returns.
func cvStressIncLoop(s *cvStressData, countImod4 uint64, n uint64) {
s.mu.Lock()
s.mu.AssertHeld()
for i := uint64(0); i != n; i++ {
s.mu.AssertHeld()
for (s.count & 3) != countImod4 {
var absDeadline time.Time = time.Now().Add(time.Duration(rand.Int31n(cvMaxDelayMicros)) * time.Microsecond)
for s.countIsIMod4[countImod4].WaitWithDeadline(&s.mu, absDeadline, nil) != nsync.OK && (s.count&3) != countImod4 {
s.mu.AssertHeld()
s.timeouts++
s.mu.AssertHeld()
absDeadline = time.Now().Add(time.Duration(rand.Int31n(cvMaxDelayMicros)) * time.Microsecond)
}
}
s.mu.AssertHeld()
s.count++
s.countIsIMod4[s.count&3].Signal()
}
s.refs--
if s.refs == 0 {
s.refsIsZero.Signal()
}
s.mu.AssertHeld()
s.mu.Unlock()
}
// TestCVTimeoutStress() tests many threads using a single lock, using
// WaitConditions and timeouts.
//
// It creates a cvStressData s, and then creates several threads using
// cvStressIncLoop() trying to increment s.count from 1 to 2 mod 4, from 2 to 3
// mod 4, and from 3 to 0 mod 4, using random delays. It sleeps a few seconds, ensuring
// many random timeouts by threads in cvStressIncLoop, because there is no thread
// incrementing s.count from 0 (which is 0 mod 4). It then creates several
// threads using cvStressIncLoop() trying to increment s.count from 0 to 1 mod 4.
// This allows all the threads to run to completion, since there are equal
// numbers for each condition.
// Finally, it waits for all threads to exit.
func TestCVTimeoutStress(t *testing.T) {
const loopCount = 50000
const threadsPerValue = 5
var s cvStressData
s.mu.Lock()
s.mu.AssertHeld()
// Create threads trying to increment from 1, 2, and 3 mod 4.
// They will continually hit their timeouts because s.count==0
for i := 0; i != threadsPerValue; i++ {
s.mu.AssertHeld()
s.refs++
go cvStressIncLoop(&s, 1, loopCount)
s.refs++
go cvStressIncLoop(&s, 2, loopCount)
s.refs++
go cvStressIncLoop(&s, 3, loopCount)
}
s.mu.AssertHeld()
s.mu.Unlock()
// Sleep a few seconds to cause many timeouts.
const sleepSeconds = 3
time.Sleep(sleepSeconds * time.Second)
s.mu.Lock()
s.mu.AssertHeld()
// Check that approximately the right number of timeouts have occurred.
// The 3 below is the three classes of thread produced before the Sleep().
// The factor of 1/4 is to allow for randomness and slow test machines.
expectedTimeouts := uint64(threadsPerValue * 3 * sleepSeconds * cvExpectedTimeoutsPerSec / 4)
timeoutsSeen := s.timeouts
if timeoutsSeen < expectedTimeouts {
t.Errorf("expected more than %d timeouts, got %d", expectedTimeouts, timeoutsSeen)
}
// Now create the threads that increment from 0 mod 4. s.count will then be incremented.
for i := 0; i != threadsPerValue; i++ {
s.mu.AssertHeld()
s.refs++
go cvStressIncLoop(&s, 0, loopCount)
}
// Wait for threads to exit.
s.mu.AssertHeld()
for s.refs != 0 {
s.refsIsZero.Wait(&s.mu)
}
s.mu.AssertHeld()
if s.refs != 0 {
t.Fatalf(fmt.Sprintf("s.refs == %d; expected 0 at end of TestCVWaitStress", s.refs))
}
s.mu.AssertHeld()
s.mu.Unlock()
// Check that s.count has the right value.
expectedCount := uint64(loopCount * threadsPerValue * 4)
if s.count != expectedCount {
t.Errorf("expected to increment s.count to %d, got %d", expectedCount, s.count)
}
// Some timeouts shoud have happened while the counts were being incremented.
expectedTimeouts = timeoutsSeen + 1000
if s.timeouts < expectedTimeouts {
t.Errorf("expected more than %d timeouts, got %d", expectedTimeouts, s.timeouts)
}
}